Back to index

lightning-sunbird  0.9+nobinonly
nsWebBrowserPersist.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 the Mozilla browser.
00016  *
00017  * The Initial Developer of the Original Code is
00018  * Netscape Communications, Inc.
00019  * Portions created by the Initial Developer are Copyright (C) 1999
00020  * the Initial Developer. All Rights Reserved.
00021  *
00022  * Contributor(s):
00023  *   Adam Lock <adamlock@netscape.com>
00024  *   Kathleen Brade <brade@netscape.com>
00025  *
00026  * Alternatively, the contents of this file may be used under the terms of
00027  * either the GNU General Public License Version 2 or later (the "GPL"), or
00028  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
00029  * in which case the provisions of the GPL or the LGPL are applicable instead
00030  * of those above. If you wish to allow use of your version of this file only
00031  * under the terms of either the GPL or the LGPL, and not to allow others to
00032  * use your version of this file under the terms of the MPL, indicate your
00033  * decision by deleting the provisions above and replace them with the notice
00034  * and other provisions required by the GPL or the LGPL. If you do not delete
00035  * the provisions above, a recipient may use your version of this file under
00036  * the terms of any one of the MPL, the GPL or the LGPL.
00037  *
00038  * ***** END LICENSE BLOCK ***** */
00039 
00040 #include "nspr.h"
00041 
00042 #include "nsIFileStreams.h"       // New Necko file streams
00043 
00044 #ifdef XP_MAC
00045 #include "nsILocalFileMac.h"
00046 #endif
00047 #ifdef XP_OS2
00048 #include "nsILocalFileOS2.h"
00049 #endif
00050 
00051 #include "nsNetUtil.h"
00052 #include "nsComponentManagerUtils.h"
00053 #include "nsIComponentRegistrar.h"
00054 #include "nsIStorageStream.h"
00055 #include "nsISeekableStream.h"
00056 #include "nsIHttpChannel.h"
00057 #include "nsIEncodedChannel.h"
00058 #include "nsIUploadChannel.h"
00059 #include "nsICachingChannel.h"
00060 #include "nsEscape.h"
00061 #include "nsUnicharUtils.h"
00062 #include "nsIStringEnumerator.h"
00063 #include "nsCRT.h"
00064 #include "nsSupportsArray.h"
00065 #include "nsInt64.h"
00066 
00067 #include "nsCExternalHandlerService.h"
00068 
00069 #include "nsIURL.h"
00070 #include "nsIFileURL.h"
00071 #include "nsIDocument.h"
00072 #include "nsIDOMDocument.h"
00073 #include "nsIDOMXMLDocument.h"
00074 #include "nsIDOMDocumentTraversal.h"
00075 #include "nsIDOMTreeWalker.h"
00076 #include "nsIDOMNode.h"
00077 #include "nsIDOMComment.h"
00078 #include "nsIDOMNamedNodeMap.h"
00079 #include "nsIDOMNodeList.h"
00080 #include "nsIDOMNSDocument.h"
00081 #include "nsIWebProgressListener.h"
00082 #include "nsIAuthPrompt.h"
00083 #include "nsIPrompt.h"
00084 #include "nsISHEntry.h"
00085 #include "nsIWebPageDescriptor.h"
00086 
00087 #include "nsIDOMNodeFilter.h"
00088 #include "nsIDOMProcessingInstruction.h"
00089 #include "nsIDOMHTMLBodyElement.h"
00090 #include "nsIDOMHTMLTableElement.h"
00091 #include "nsIDOMHTMLTableRowElement.h"
00092 #include "nsIDOMHTMLTableCellElement.h"
00093 #include "nsIDOMHTMLAnchorElement.h"
00094 #include "nsIDOMHTMLAreaElement.h"
00095 #include "nsIDOMHTMLImageElement.h"
00096 #include "nsIDOMHTMLScriptElement.h"
00097 #include "nsIDOMHTMLLinkElement.h"
00098 #include "nsIDOMHTMLBaseElement.h"
00099 #include "nsIDOMHTMLFrameElement.h"
00100 #include "nsIDOMHTMLIFrameElement.h"
00101 #include "nsIDOMHTMLInputElement.h"
00102 #include "nsIDOMHTMLEmbedElement.h"
00103 #include "nsIDOMHTMLObjectElement.h"
00104 #include "nsIDOMHTMLAppletElement.h"
00105 #include "nsIDOMHTMLDocument.h"
00106 
00107 #include "nsIImageLoadingContent.h"
00108 
00109 #include "ftpCore.h"
00110 #include "nsITransport.h"
00111 #include "nsISocketTransport.h"
00112 #include "nsIStringBundle.h"
00113 
00114 #include "nsWebBrowserPersist.h"
00115 
00116 
00117 #define NS_SUCCESS_DONT_FIXUP NS_ERROR_GENERATE_SUCCESS(NS_ERROR_MODULE_GENERAL, 1)
00118 
00119 // Information about a DOM document
00120 struct DocData
00121 {
00122     nsCOMPtr<nsIURI> mBaseURI;
00123     nsCOMPtr<nsIDOMDocument> mDocument;
00124     nsCOMPtr<nsIURI> mFile;
00125     nsCOMPtr<nsIURI> mDataPath;
00126     PRPackedBool mDataPathIsRelative;
00127     nsCString mRelativePathToData;
00128     nsCString mCharset;
00129 };
00130 
00131 // Information about a URI
00132 struct URIData
00133 {
00134     PRPackedBool mNeedsPersisting;
00135     PRPackedBool mSaved;
00136     PRPackedBool mIsSubFrame;
00137     PRPackedBool mDataPathIsRelative;
00138     PRPackedBool mNeedsFixup;
00139     nsString mFilename;
00140     nsString mSubFrameExt;
00141     nsCOMPtr<nsIURI> mFile;
00142     nsCOMPtr<nsIURI> mDataPath;
00143     nsCString mRelativePathToData;
00144     nsCString mCharset;
00145 };
00146 
00147 // Information about the output stream
00148 struct OutputData
00149 {
00150     nsCOMPtr<nsIURI> mFile;
00151     nsCOMPtr<nsIURI> mOriginalLocation;
00152     nsCOMPtr<nsIOutputStream> mStream;
00153     nsInt64 mSelfProgress;
00154     nsInt64 mSelfProgressMax;
00155     PRPackedBool mCalcFileExt;
00156 
00157     OutputData(nsIURI *aFile, nsIURI *aOriginalLocation, PRBool aCalcFileExt) :
00158         mFile(aFile),
00159         mOriginalLocation(aOriginalLocation),
00160         mSelfProgress(0),
00161         mSelfProgressMax(10000),
00162         mCalcFileExt(aCalcFileExt)
00163     {
00164     }
00165     ~OutputData()
00166     {
00167         if (mStream)
00168         {
00169             mStream->Close();
00170         }
00171     }
00172 };
00173 
00174 struct UploadData
00175 {
00176     nsCOMPtr<nsIURI> mFile;
00177     nsInt64 mSelfProgress;
00178     nsInt64 mSelfProgressMax;
00179 
00180     UploadData(nsIURI *aFile) :
00181         mFile(aFile),
00182         mSelfProgress(0),
00183         mSelfProgressMax(10000)
00184     {
00185     }
00186 };
00187 
00188 struct CleanupData
00189 {
00190     nsCOMPtr<nsILocalFile> mFile;
00191     // Snapshot of what the file actually is at the time of creation so that if
00192     // it transmutes into something else later on it can be ignored. For example,
00193     // catch files that turn into dirs or vice versa.
00194     PRPackedBool mIsDirectory;
00195 };
00196 
00197 // Maximum file length constant. The max file name length is
00198 // volume / server dependent but it is difficult to obtain
00199 // that information. Instead this constant is a reasonable value that
00200 // modern systems should able to cope with.
00201 
00202 #ifdef XP_MAC
00203 const PRUint32 kDefaultMaxFilenameLength = 31;
00204 #else
00205 const PRUint32 kDefaultMaxFilenameLength = 64;
00206 #endif
00207 
00208 // Schemes that cannot be saved because they contain no useful content
00209 const char *kNonpersistableSchemes[] = {
00210     "about:",
00211     "news:", 
00212     "snews:",
00213     "ldap:",
00214     "ldaps:",
00215     "mailto:", 
00216     "finger:",
00217     "telnet:", 
00218     "gopher:", 
00219     "javascript:",
00220     "view-source:",
00221     "irc:",
00222     "mailbox:"
00223 };
00224 const PRUint32 kNonpersistableSchemesSize = sizeof(kNonpersistableSchemes) / sizeof(kNonpersistableSchemes[0]);
00225 
00226 // Default flags for persistence
00227 const PRUint32 kDefaultPersistFlags = 
00228     nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION |
00229     nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES;
00230 
00231 // String bundle where error messages come from
00232 const char *kWebBrowserPersistStringBundle =
00233     "chrome://global/locale/nsWebBrowserPersist.properties";
00234 
00235 nsWebBrowserPersist::nsWebBrowserPersist() :
00236     mCurrentThingsToPersist(0),
00237     mFirstAndOnlyUse(PR_TRUE),
00238     mCancel(PR_FALSE),
00239     mJustStartedLoading(PR_TRUE),
00240     mCompleted(PR_FALSE),
00241     mStartSaving(PR_FALSE),
00242     mReplaceExisting(PR_TRUE),
00243     mSerializingOutput(PR_FALSE),
00244     mPersistFlags(kDefaultPersistFlags),
00245     mPersistResult(NS_OK),
00246     mWrapColumn(72),
00247     mEncodingFlags(0)
00248 {
00249 }
00250 
00251 nsWebBrowserPersist::~nsWebBrowserPersist()
00252 {
00253     Cleanup();
00254 }
00255 
00256 //*****************************************************************************
00257 // nsWebBrowserPersist::nsISupports
00258 //*****************************************************************************
00259 
00260 NS_IMPL_ADDREF(nsWebBrowserPersist)
00261 NS_IMPL_RELEASE(nsWebBrowserPersist)
00262 
00263 NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist)
00264     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist)
00265     NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist)
00266     NS_INTERFACE_MAP_ENTRY(nsICancelable)
00267     NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
00268     NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
00269     NS_INTERFACE_MAP_ENTRY(nsIStreamListener)
00270     NS_INTERFACE_MAP_ENTRY(nsIRequestObserver)
00271     NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink)
00272 NS_INTERFACE_MAP_END
00273 
00274 
00275 //*****************************************************************************
00276 // nsWebBrowserPersist::nsIInterfaceRequestor
00277 //*****************************************************************************
00278 
00279 NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID & aIID, void **aIFace)
00280 {
00281     NS_ENSURE_ARG_POINTER(aIFace);
00282 
00283     *aIFace = nsnull;
00284 
00285     nsresult rv = QueryInterface(aIID, aIFace);
00286     if (NS_SUCCEEDED(rv))
00287     {
00288         return rv;
00289     }
00290     
00291     if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) 
00292                              || aIID.Equals(NS_GET_IID(nsIPrompt))))
00293     {
00294         mProgressListener->QueryInterface(aIID, aIFace);
00295         if (*aIFace)
00296             return NS_OK;
00297     }
00298 
00299     nsCOMPtr<nsIInterfaceRequestor> req = do_QueryInterface(mProgressListener);
00300     if (req)
00301     {
00302         return req->GetInterface(aIID, aIFace);
00303     }
00304 
00305     return NS_ERROR_NO_INTERFACE;
00306 }
00307 
00308 
00309 //*****************************************************************************
00310 // nsWebBrowserPersist::nsIWebBrowserPersist
00311 //*****************************************************************************
00312 
00313 /* attribute unsigned long persistFlags; */
00314 NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(PRUint32 *aPersistFlags)
00315 {
00316     NS_ENSURE_ARG_POINTER(aPersistFlags);
00317     *aPersistFlags = mPersistFlags;
00318     return NS_OK;
00319 }
00320 NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(PRUint32 aPersistFlags)
00321 {
00322     mPersistFlags = aPersistFlags;
00323     mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES) ? PR_TRUE : PR_FALSE;
00324     mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT) ? PR_TRUE : PR_FALSE;
00325     return NS_OK;
00326 }
00327 
00328 /* readonly attribute unsigned long currentState; */
00329 NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(PRUint32 *aCurrentState)
00330 {
00331     NS_ENSURE_ARG_POINTER(aCurrentState);
00332     if (mCompleted)
00333     {
00334         *aCurrentState = PERSIST_STATE_FINISHED;
00335     }
00336     else if (mFirstAndOnlyUse)
00337     {
00338         *aCurrentState = PERSIST_STATE_SAVING;
00339     }
00340     else
00341     {
00342         *aCurrentState = PERSIST_STATE_READY;
00343     }
00344     return NS_OK;
00345 }
00346 
00347 /* readonly attribute unsigned long result; */
00348 NS_IMETHODIMP nsWebBrowserPersist::GetResult(PRUint32 *aResult)
00349 {
00350     NS_ENSURE_ARG_POINTER(aResult);
00351     *aResult = mPersistResult;
00352     return NS_OK;
00353 }
00354 
00355 /* attribute nsIWebBrowserPersistProgress progressListener; */
00356 NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener(
00357     nsIWebProgressListener * *aProgressListener)
00358 {
00359     NS_ENSURE_ARG_POINTER(aProgressListener);
00360     *aProgressListener = mProgressListener;
00361     NS_IF_ADDREF(*aProgressListener);
00362     return NS_OK;
00363 }
00364 
00365 NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener(
00366     nsIWebProgressListener * aProgressListener)
00367 {
00368     mProgressListener = aProgressListener;
00369     mProgressListener2 = do_QueryInterface(aProgressListener);
00370     return NS_OK;
00371 }
00372 
00373 /* void saveURI (in nsIURI aURI, in nsISupports aCacheKey, in nsIURI aReferrer,
00374    in nsIInputStream aPostData, in wstring aExtraHeaders,
00375    in nsISupports aFile); */
00376 NS_IMETHODIMP nsWebBrowserPersist::SaveURI(
00377     nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, nsIInputStream *aPostData, const char *aExtraHeaders, nsISupports *aFile)
00378 {
00379     NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
00380     mFirstAndOnlyUse = PR_FALSE; // Stop people from reusing this object!
00381 
00382     nsCOMPtr<nsIURI> fileAsURI;
00383     nsresult rv;
00384     rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
00385     NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
00386 
00387     // SaveURI doesn't like broken uris.
00388     mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
00389     rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aPostData, aExtraHeaders, fileAsURI, PR_FALSE);
00390     return NS_FAILED(rv) ? rv : NS_OK;
00391 }
00392 
00393 /* void saveChannel (in nsIChannel aChannel, in nsISupports aFile); */
00394 NS_IMETHODIMP nsWebBrowserPersist::SaveChannel(
00395     nsIChannel *aChannel, nsISupports *aFile)
00396 {
00397     NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
00398     mFirstAndOnlyUse = PR_FALSE; // Stop people from reusing this object!
00399 
00400     nsCOMPtr<nsIURI> fileAsURI;
00401     nsresult rv;
00402     rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
00403     NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
00404 
00405     rv = aChannel->GetURI(getter_AddRefs(mURI));
00406     NS_ENSURE_SUCCESS(rv, rv);
00407 
00408     // SaveURI doesn't like broken uris.
00409     mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS;
00410     rv = SaveChannelInternal(aChannel, fileAsURI, PR_FALSE);
00411     return NS_FAILED(rv) ? rv : NS_OK;
00412 }
00413 
00414 
00415 /* void saveDocument (in nsIDOMDocument aDocument, in nsIURI aFileURI,
00416    in nsIURI aDataPathURI, in string aOutputContentType,
00417    in unsigned long aEncodingFlags, in unsigned long aWrapColumn); */
00418 NS_IMETHODIMP nsWebBrowserPersist::SaveDocument(
00419     nsIDOMDocument *aDocument, nsISupports *aFile, nsISupports *aDataPath,
00420     const char *aOutputContentType, PRUint32 aEncodingFlags, PRUint32 aWrapColumn)
00421 {
00422     NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE);
00423     mFirstAndOnlyUse = PR_FALSE; // Stop people from reusing this object!
00424 
00425     nsCOMPtr<nsIURI> fileAsURI;
00426     nsCOMPtr<nsIURI> datapathAsURI;
00427     nsresult rv;
00428 
00429     rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI));
00430     NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
00431     if (aDataPath)
00432     {
00433         rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI));
00434         NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG);
00435     }
00436 
00437     mWrapColumn = aWrapColumn;
00438 
00439     // Produce nsIDocumentEncoder encoding flags
00440     mEncodingFlags = 0;
00441     if (aEncodingFlags & ENCODE_FLAGS_SELECTION_ONLY)
00442         mEncodingFlags |= nsIDocumentEncoder::OutputSelectionOnly;
00443     if (aEncodingFlags & ENCODE_FLAGS_FORMATTED)
00444         mEncodingFlags |= nsIDocumentEncoder::OutputFormatted;
00445     if (aEncodingFlags & ENCODE_FLAGS_RAW)
00446         mEncodingFlags |= nsIDocumentEncoder::OutputRaw;
00447     if (aEncodingFlags & ENCODE_FLAGS_BODY_ONLY)
00448         mEncodingFlags |= nsIDocumentEncoder::OutputBodyOnly;
00449     if (aEncodingFlags & ENCODE_FLAGS_PREFORMATTED)
00450         mEncodingFlags |= nsIDocumentEncoder::OutputPreformatted;
00451     if (aEncodingFlags & ENCODE_FLAGS_WRAP)
00452         mEncodingFlags |= nsIDocumentEncoder::OutputWrap;
00453     if (aEncodingFlags & ENCODE_FLAGS_FORMAT_FLOWED)
00454         mEncodingFlags |= nsIDocumentEncoder::OutputFormatFlowed;
00455     if (aEncodingFlags & ENCODE_FLAGS_ABSOLUTE_LINKS)
00456         mEncodingFlags |= nsIDocumentEncoder::OutputAbsoluteLinks;
00457     if (aEncodingFlags & ENCODE_FLAGS_ENCODE_BASIC_ENTITIES)
00458         mEncodingFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities;
00459     if (aEncodingFlags & ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES)
00460         mEncodingFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities;
00461     if (aEncodingFlags & ENCODE_FLAGS_ENCODE_HTML_ENTITIES)
00462         mEncodingFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities;
00463     if (aEncodingFlags & ENCODE_FLAGS_ENCODE_W3C_ENTITIES)
00464         mEncodingFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities;
00465     if (aEncodingFlags & ENCODE_FLAGS_CR_LINEBREAKS)
00466         mEncodingFlags |= nsIDocumentEncoder::OutputCRLineBreak;
00467     if (aEncodingFlags & ENCODE_FLAGS_LF_LINEBREAKS)
00468         mEncodingFlags |= nsIDocumentEncoder::OutputLFLineBreak;
00469     if (aEncodingFlags & ENCODE_FLAGS_NOSCRIPT_CONTENT)
00470         mEncodingFlags |= nsIDocumentEncoder::OutputNoScriptContent;
00471     if (aEncodingFlags & ENCODE_FLAGS_NOFRAMES_CONTENT)
00472         mEncodingFlags |= nsIDocumentEncoder::OutputNoFramesContent;
00473     
00474     if (aOutputContentType)
00475     {
00476         mContentType.AssignASCII(aOutputContentType);
00477     }
00478 
00479     rv = SaveDocumentInternal(aDocument, fileAsURI, datapathAsURI);
00480 
00481     // Now save the URIs that have been gathered
00482 
00483     if (NS_SUCCEEDED(rv) && datapathAsURI)
00484     {
00485         rv = SaveGatheredURIs(fileAsURI);
00486     }
00487     else if (mProgressListener)
00488     {
00489         // tell the listener we're done
00490         mProgressListener->OnStateChange(nsnull, nsnull,
00491                                          nsIWebProgressListener::STATE_START,
00492                                          NS_OK);
00493         mProgressListener->OnStateChange(nsnull, nsnull,
00494                                          nsIWebProgressListener::STATE_STOP,
00495                                          rv);
00496     }
00497 
00498     return rv;
00499 }
00500 
00501 /* void cancel(nsresult aReason); */
00502 NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason)
00503 {
00504     mCancel = PR_TRUE;
00505     EndDownload(aReason);
00506     return NS_OK;
00507 }
00508 
00509 
00510 /* void cancelSave(); */
00511 NS_IMETHODIMP nsWebBrowserPersist::CancelSave()
00512 {
00513     return Cancel(NS_BINDING_ABORTED);
00514 }
00515 
00516 
00517 nsresult
00518 nsWebBrowserPersist::StartUpload(nsIStorageStream *storStream, 
00519     nsIURI *aDestinationURI, const nsACString &aContentType)
00520 {
00521      // setup the upload channel if the destination is not local
00522     nsCOMPtr<nsIInputStream> inputstream;
00523     nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream));
00524     NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE);
00525     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00526 
00527     nsCOMPtr<nsIChannel> destChannel;
00528     rv = CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel));
00529     nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(destChannel));
00530     NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE);
00531 
00532     // Set the upload stream
00533     // NOTE: ALL data must be available in "inputstream"
00534     rv = uploadChannel->SetUploadStream(inputstream, aContentType, -1);
00535     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00536     rv = destChannel->AsyncOpen(this, nsnull);
00537     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00538 
00539     // add this to the upload list
00540     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(destChannel);
00541     nsISupportsKey key(keyPtr);
00542     mUploadList.Put(&key, new UploadData(aDestinationURI));
00543 
00544     return NS_OK;
00545 }
00546 
00547 nsresult
00548 nsWebBrowserPersist::SaveGatheredURIs(nsIURI *aFileAsURI)
00549 {
00550     nsresult rv = NS_OK;
00551 
00552     // Count how many URIs in the URI map require persisting
00553     PRUint32 urisToPersist = 0;
00554     if (mURIMap.Count() > 0)
00555     {
00556         mURIMap.Enumerate(EnumCountURIsToPersist, &urisToPersist);
00557     }
00558 
00559     if (urisToPersist > 0)
00560     {
00561         // Persist each file in the uri map. The document(s)
00562         // will be saved after the last one of these is saved.
00563         mURIMap.Enumerate(EnumPersistURIs, this);
00564     }
00565 
00566     // if we don't have anything in mOutputMap (added from above enumeration)
00567     // then we build the doc list (SaveDocuments)
00568     if (mOutputMap.Count() == 0)
00569     {
00570         // There are no URIs to save, so just save the document(s)
00571 
00572         // State start notification
00573         PRUint32 addToStateFlags = 0;
00574         if (mProgressListener)
00575         {
00576             if (mJustStartedLoading)
00577             {
00578                 addToStateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
00579             }
00580             mProgressListener->OnStateChange(nsnull, nsnull,
00581                 nsIWebProgressListener::STATE_START | addToStateFlags, NS_OK);
00582         }
00583 
00584         rv = SaveDocuments();
00585         if (NS_FAILED(rv))
00586             EndDownload(rv);
00587         else if (aFileAsURI)
00588         {
00589             // local files won't trigger OnStopRequest so we call EndDownload here
00590             PRBool isFile = PR_FALSE;
00591             aFileAsURI->SchemeIs("file", &isFile);
00592             if (isFile)
00593                 EndDownload(NS_OK);
00594         }
00595 
00596         // State stop notification
00597         if (mProgressListener)
00598         {
00599             mProgressListener->OnStateChange(nsnull, nsnull,
00600                 nsIWebProgressListener::STATE_STOP | addToStateFlags, rv);
00601         }
00602     }
00603 
00604     return rv;
00605 }
00606 
00607 // this method returns true if there is another file to persist and false if not
00608 PRBool
00609 nsWebBrowserPersist::SerializeNextFile()
00610 {
00611     if (!mSerializingOutput)
00612     {
00613         return PR_FALSE;
00614     }
00615 
00616     nsresult rv = SaveGatheredURIs(nsnull);
00617     if (NS_FAILED(rv))
00618     {
00619         return PR_FALSE;
00620     }
00621 
00622     return (mURIMap.Count() 
00623         || mUploadList.Count()
00624         || mDocList.Count()
00625         || mOutputMap.Count());
00626 }
00627 
00628 
00629 //*****************************************************************************
00630 // nsWebBrowserPersist::nsIRequestObserver
00631 //*****************************************************************************
00632 
00633 NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest(
00634     nsIRequest* request, nsISupports *ctxt)
00635 {
00636     if (mProgressListener)
00637     {
00638         PRUint32 stateFlags = nsIWebProgressListener::STATE_START |
00639                               nsIWebProgressListener::STATE_IS_REQUEST;
00640         if (mJustStartedLoading)
00641         {
00642             stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
00643         }
00644         mProgressListener->OnStateChange(nsnull, request, stateFlags, NS_OK);
00645     }
00646 
00647     mJustStartedLoading = PR_FALSE;
00648 
00649     nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
00650     NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
00651 
00652     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
00653     nsISupportsKey key(keyPtr);
00654     OutputData *data = (OutputData *) mOutputMap.Get(&key);
00655 
00656     // NOTE: This code uses the channel as a hash key so it will not
00657     //       recognize redirected channels because the key is not the same.
00658     //       When that happens we remove and add the data entry to use the
00659     //       new channel as the hash key.
00660     if (!data)
00661     {
00662         UploadData *upData = (UploadData *) mUploadList.Get(&key);
00663         if (!upData)
00664         {
00665             // Redirect? Try and fixup the output table
00666             nsresult rv = FixRedirectedChannelEntry(channel);
00667             NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00668 
00669             // Should be able to find the data after fixup unless redirects
00670             // are disabled.
00671             data = (OutputData *) mOutputMap.Get(&key);
00672             if (!data)
00673             {
00674                 return NS_ERROR_FAILURE;
00675             }
00676         }
00677     }
00678 
00679     if (data && data->mFile)
00680     {
00681         // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags,
00682         // try to determine whether this channel needs to apply Content-Encoding
00683         // conversions.
00684         NS_ASSERTION(!((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) &&
00685                       (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)),
00686                      "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set");
00687         if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION)
00688             SetApplyConversionIfNeeded(channel);
00689 
00690         if (data->mCalcFileExt && !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES))
00691         {
00692             // this is the first point at which the server can tell us the mimetype
00693             CalculateAndAppendFileExt(data->mFile, channel, data->mOriginalLocation);
00694 
00695             // now make filename conformant and unique
00696             CalculateUniqueFilename(data->mFile);
00697         }
00698 
00699         // compare uris and bail before we add to output map if they are equal
00700         PRBool isEqual = PR_FALSE;
00701         if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual))
00702             && isEqual)
00703         {
00704             // remove from output map
00705             delete data;
00706             mOutputMap.Remove(&key);
00707 
00708             // cancel; we don't need to know any more
00709             // stop request will get called
00710             request->Cancel(NS_BINDING_ABORTED);
00711         }
00712     }
00713 
00714     return NS_OK;
00715 }
00716  
00717 NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest(
00718     nsIRequest* request, nsISupports *ctxt, nsresult status)
00719 {
00720     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
00721     nsISupportsKey key(keyPtr);
00722     OutputData *data = (OutputData *) mOutputMap.Get(&key);
00723     if (data)
00724     {
00725 #if defined(XP_OS2)
00726         // delete 'data';  this will close the stream and let
00727         // us tag the file it created with its source URI
00728         nsCOMPtr<nsIURI> uriSource = data->mOriginalLocation;
00729         nsCOMPtr<nsILocalFile> localFile;
00730         GetLocalFileFromURI(data->mFile, getter_AddRefs(localFile));
00731         delete data;
00732         mOutputMap.Remove(&key);
00733         if (localFile)
00734         {
00735             nsCOMPtr<nsILocalFileOS2> localFileOS2 = do_QueryInterface(localFile);
00736             if (localFileOS2)
00737             {
00738                 nsCAutoString url;
00739                 uriSource->GetSpec(url);
00740                 localFileOS2->SetFileSource(url);
00741             }
00742         }
00743 #else
00744         // This will close automatically close the output stream
00745         delete data;
00746         mOutputMap.Remove(&key);
00747 #endif
00748     }
00749     else
00750     {
00751         // if we didn't find the data in mOutputMap, try mUploadList
00752         UploadData *upData = (UploadData *) mUploadList.Get(&key);
00753         if (upData)
00754         {
00755             delete upData;
00756             mUploadList.Remove(&key);
00757         }
00758     }
00759 
00760     // ensure we call SaveDocuments if we:
00761     // 1) aren't canceling
00762     // 2) we haven't triggered the save (which we only want to trigger once)
00763     // 3) we aren't serializing (which will call it inside SerializeNextFile)
00764     if (mOutputMap.Count() == 0 && !mCancel && !mStartSaving && !mSerializingOutput)
00765     {
00766         nsresult rv = SaveDocuments();
00767         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
00768     }
00769 
00770     PRBool completed = PR_FALSE;
00771     if (mOutputMap.Count() == 0 && mUploadList.Count() == 0 && !mCancel)
00772     {
00773         // if no documents left in mDocList, --> done
00774         // if we have no files left to serialize and no error result, --> done
00775         if (mDocList.Count() == 0
00776             || (!SerializeNextFile() && NS_SUCCEEDED(mPersistResult)))
00777         {
00778             completed = PR_TRUE;
00779         }
00780     }
00781 
00782     if (completed)
00783     {
00784         // we're all done, do our cleanup
00785         EndDownload(NS_OK);
00786     }
00787 
00788     if (mProgressListener)
00789     {
00790         PRUint32 stateFlags = nsIWebProgressListener::STATE_STOP |
00791                               nsIWebProgressListener::STATE_IS_REQUEST;
00792         if (completed)
00793         {
00794             stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK;
00795         }
00796         mProgressListener->OnStateChange(nsnull, request, stateFlags, status);
00797     }
00798     if (completed)
00799     {
00800         mProgressListener = nsnull;
00801         mProgressListener2 = nsnull;
00802     }
00803 
00804     return NS_OK;
00805 }
00806 
00807 //*****************************************************************************
00808 // nsWebBrowserPersist::nsIStreamListener
00809 //*****************************************************************************
00810 
00811 static NS_METHOD DiscardSegments(nsIInputStream *input,
00812                                  void *closure,
00813                                  const char *buf,
00814                                  PRUint32 offset,
00815                                  PRUint32 count,
00816                                  PRUint32 *countRead)
00817 {
00818     *countRead = count;
00819     return NS_OK;
00820 }
00821 
00822 NS_IMETHODIMP nsWebBrowserPersist::OnDataAvailable(
00823     nsIRequest* request, nsISupports *aContext, nsIInputStream *aIStream,
00824     PRUint32 aOffset, PRUint32 aLength)
00825 {
00826     PRBool cancel = mCancel;
00827     if (!cancel)
00828     {
00829         nsresult rv = NS_OK;
00830         PRUint32 bytesRemaining = aLength;
00831 
00832         nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
00833         NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE);
00834 
00835         nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
00836         nsISupportsKey key(keyPtr);
00837         OutputData *data = (OutputData *) mOutputMap.Get(&key);
00838         if (!data) {
00839             // might be uploadData; consume necko's buffer and bail...
00840             PRUint32 n;
00841             return aIStream->ReadSegments(DiscardSegments, nsnull, aLength, &n);
00842         }
00843 
00844         PRBool readError = PR_TRUE;
00845 
00846         // Make the output stream
00847         if (!data->mStream)
00848         {
00849             rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream));
00850             if (NS_FAILED(rv))
00851             {
00852                 readError = PR_FALSE;
00853                 cancel = PR_TRUE;
00854             }
00855         }
00856 
00857         // Read data from the input and write to the output
00858         char buffer[8192];
00859         PRUint32 bytesRead;
00860         while (!cancel && bytesRemaining)
00861         {
00862             readError = PR_TRUE;
00863             rv = aIStream->Read(buffer, PR_MIN(sizeof(buffer), bytesRemaining), &bytesRead);
00864             if (NS_SUCCEEDED(rv))
00865             {
00866                 readError = PR_FALSE;
00867                 // Write out the data until something goes wrong, or, it is
00868                 // all written.  We loop because for some errors (e.g., disk
00869                 // full), we get NS_OK with some bytes written, then an error.
00870                 // So, we want to write again in that case to get the actual
00871                 // error code.
00872                 const char *bufPtr = buffer; // Where to write from.
00873                 while (NS_SUCCEEDED(rv) && bytesRead)
00874                 {
00875                     PRUint32 bytesWritten = 0;
00876                     rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten);
00877                     if (NS_SUCCEEDED(rv))
00878                     {
00879                         bytesRead -= bytesWritten;
00880                         bufPtr += bytesWritten;
00881                         bytesRemaining -= bytesWritten;
00882                         // Force an error if (for some reason) we get NS_OK but
00883                         // no bytes written.
00884                         if (!bytesWritten)
00885                         {
00886                             rv = NS_ERROR_FAILURE;
00887                             cancel = PR_TRUE;
00888                         }
00889                     }
00890                     else
00891                     {
00892                         // Disaster - can't write out the bytes - disk full / permission?
00893                         cancel = PR_TRUE;
00894                     }
00895                 }
00896             }
00897             else
00898             {
00899                 // Disaster - can't read the bytes - broken link / file error?
00900                 cancel = PR_TRUE;
00901             }
00902         }
00903 
00904         PRInt32 channelContentLength = -1;
00905         if (!cancel &&
00906             NS_SUCCEEDED(channel->GetContentLength(&channelContentLength)))
00907         {
00908             // if we get -1 at this point, we didn't get content-length header
00909             // assume that we got all of the data and push what we have; 
00910             // that's the best we can do now
00911             if ((-1 == channelContentLength) ||
00912                 ((channelContentLength - (aOffset + aLength)) == 0))
00913             {
00914                 NS_ASSERTION(channelContentLength != -1, "no content length");
00915                 // we're done with this pass; see if we need to do upload
00916                 nsCAutoString contentType;
00917                 channel->GetContentType(contentType);
00918                 // if we don't have the right type of output stream then it's a local file
00919                 nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(data->mStream));
00920                 if (storStream)
00921                 {
00922                     data->mStream->Close();
00923                     data->mStream = nsnull; // null out stream so we don't close it later
00924                     rv = StartUpload(storStream, data->mFile, contentType);
00925                     if (NS_FAILED(rv))
00926                     {
00927                         cancel = PR_TRUE;
00928                     }
00929                 }
00930             }
00931         }
00932 
00933         // Notify listener if an error occurred.
00934         if (cancel)
00935         {
00936             SendErrorStatusChange(readError, rv,
00937                 readError ? request : nsnull, data->mFile);
00938         }
00939     }
00940 
00941     // Cancel reading?
00942     if (cancel)
00943     {
00944         EndDownload(NS_BINDING_ABORTED);
00945     }
00946 
00947     return NS_OK;
00948 }
00949 
00950 
00951 //*****************************************************************************
00952 // nsWebBrowserPersist::nsIProgressEventSink
00953 //*****************************************************************************
00954 
00955 /* void onProgress (in nsIRequest request, in nsISupports ctxt,
00956     in unsigned long long aProgress, in unsigned long long aProgressMax); */
00957 NS_IMETHODIMP nsWebBrowserPersist::OnProgress(
00958     nsIRequest *request, nsISupports *ctxt, PRUint64 aProgress,
00959     PRUint64 aProgressMax)
00960 {
00961     if (!mProgressListener)
00962     {
00963         return NS_OK;
00964     }
00965 
00966     // Store the progress of this request
00967     nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(request);
00968     nsISupportsKey key(keyPtr);
00969     OutputData *data = (OutputData *) mOutputMap.Get(&key);
00970     if (data)
00971     {
00972         data->mSelfProgress = PRInt64(aProgress);
00973         data->mSelfProgressMax = PRInt64(aProgressMax);
00974     }
00975     else
00976     {
00977         UploadData *upData = (UploadData *) mUploadList.Get(&key);
00978         if (upData)
00979         {
00980             upData->mSelfProgress = PRInt64(aProgress);
00981             upData->mSelfProgressMax = PRInt64(aProgressMax);
00982         }
00983     }
00984 
00985     // Notify listener of total progress
00986     CalcTotalProgress();
00987     if (mProgressListener2)
00988     {
00989       mProgressListener2->OnProgressChange64(nsnull, request, aProgress,
00990             aProgressMax, mTotalCurrentProgress, mTotalMaxProgress);
00991     }
00992     else
00993     {
00994       // have to truncate 64-bit to 32bit
00995       mProgressListener->OnProgressChange(nsnull, request, nsUint64(aProgress),
00996               nsUint64(aProgressMax), mTotalCurrentProgress, mTotalMaxProgress);
00997     }
00998 
00999     return NS_OK;
01000 
01001 }
01002 
01003 /* void onStatus (in nsIRequest request, in nsISupports ctxt,
01004     in nsresult status, in wstring statusArg); */
01005 NS_IMETHODIMP nsWebBrowserPersist::OnStatus(
01006     nsIRequest *request, nsISupports *ctxt, nsresult status,
01007     const PRUnichar *statusArg)
01008 {
01009     if (mProgressListener)
01010     {
01011         // We need to filter out non-error error codes.
01012         // Is the only NS_SUCCEEDED value NS_OK?
01013         switch ( status )
01014         {
01015         case NS_NET_STATUS_RESOLVING_HOST:
01016         case NS_NET_STATUS_BEGIN_FTP_TRANSACTION:
01017         case NS_NET_STATUS_END_FTP_TRANSACTION:
01018         case NS_NET_STATUS_CONNECTING_TO:
01019         case NS_NET_STATUS_CONNECTED_TO:
01020         case NS_NET_STATUS_SENDING_TO:
01021         case NS_NET_STATUS_RECEIVING_FROM:
01022         case NS_NET_STATUS_WAITING_FOR:
01023         case nsITransport::STATUS_READING:
01024         case nsITransport::STATUS_WRITING:
01025             break;
01026 
01027         default:
01028             // Pass other notifications (for legitimate errors) along.
01029             mProgressListener->OnStatusChange(nsnull, request, status, statusArg);
01030             break;
01031         }
01032 
01033     }
01034     return NS_OK;
01035 }
01036 
01037 
01038 //*****************************************************************************
01039 // nsWebBrowserPersist private methods
01040 //*****************************************************************************
01041 
01042 // Convert error info into proper message text and send OnStatusChange notification
01043 // to the web progress listener.
01044 nsresult nsWebBrowserPersist::SendErrorStatusChange( 
01045     PRBool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI)
01046 {
01047     NS_ENSURE_ARG_POINTER(aURI);
01048 
01049     if (!mProgressListener)
01050     {
01051         // Do nothing
01052         return NS_OK;
01053     }
01054 
01055     // Get the file path or spec from the supplied URI
01056     nsCOMPtr<nsILocalFile> file;
01057     GetLocalFileFromURI(aURI, getter_AddRefs(file));
01058     nsAutoString path;
01059     if (file)
01060     {
01061         file->GetPath(path);
01062     }
01063     else
01064     {
01065         nsCAutoString fileurl;
01066         aURI->GetSpec(fileurl);
01067         AppendUTF8toUTF16(fileurl, path);
01068     }
01069     
01070     nsAutoString msgId;
01071     switch(aResult)
01072     {
01073     case NS_ERROR_FILE_NAME_TOO_LONG:
01074         // File name too long.
01075         msgId.AssignLiteral("fileNameTooLongError");
01076         break;
01077     case NS_ERROR_FILE_ALREADY_EXISTS:
01078         // File exists with same name as directory.
01079         msgId.AssignLiteral("fileAlreadyExistsError");
01080         break;
01081     case NS_ERROR_FILE_DISK_FULL:
01082     case NS_ERROR_FILE_NO_DEVICE_SPACE:
01083         // Out of space on target volume.
01084         msgId.AssignLiteral("diskFull");
01085         break;
01086 
01087     case NS_ERROR_FILE_READ_ONLY:
01088         // Attempt to write to read/only file.
01089         msgId.AssignLiteral("readOnly");
01090         break;
01091 
01092     case NS_ERROR_FILE_ACCESS_DENIED:
01093         // Attempt to write without sufficient permissions.
01094         msgId.AssignLiteral("accessError");
01095         break;
01096 
01097     default:
01098         // Generic read/write error message.
01099         if (aIsReadError)
01100             msgId.AssignLiteral("readError");
01101         else
01102             msgId.AssignLiteral("writeError");
01103         break;
01104     }
01105     // Get properties file bundle and extract status string.
01106     nsresult rv;
01107     nsCOMPtr<nsIStringBundleService> s = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv);
01108     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE);
01109 
01110     nsCOMPtr<nsIStringBundle> bundle;
01111     rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle));
01112     NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE);
01113     
01114     nsXPIDLString msgText;
01115     const PRUnichar *strings[1];
01116     strings[0] = path.get();
01117     rv = bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText));
01118     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01119 
01120     mProgressListener->OnStatusChange(nsnull, aRequest, aResult, msgText);
01121 
01122     return NS_OK;
01123 }
01124 
01125 nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const
01126 {
01127     NS_ENSURE_ARG_POINTER(aObject);
01128     NS_ENSURE_ARG_POINTER(aURI);
01129     
01130     nsCOMPtr<nsIFile> objAsFile = do_QueryInterface(aObject);
01131     if (objAsFile)
01132     {
01133         return NS_NewFileURI(aURI, objAsFile);
01134     }
01135     nsCOMPtr<nsIURI> objAsURI = do_QueryInterface(aObject);
01136     if (objAsURI)
01137     {
01138         *aURI = objAsURI;
01139         NS_ADDREF(*aURI);
01140         return NS_OK;
01141     }
01142 
01143     return NS_ERROR_FAILURE;
01144 }
01145 
01146 nsresult nsWebBrowserPersist::GetLocalFileFromURI(nsIURI *aURI, nsILocalFile **aLocalFile) const
01147 {
01148     nsresult rv;
01149 
01150     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
01151     if (NS_FAILED(rv))
01152         return rv;
01153 
01154     nsCOMPtr<nsIFile> file;
01155     rv = fileURL->GetFile(getter_AddRefs(file));
01156     if (NS_SUCCEEDED(rv))
01157         rv = CallQueryInterface(file, aLocalFile);
01158 
01159     return rv;
01160 }
01161 
01162 nsresult nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath) const
01163 {
01164     NS_ENSURE_ARG_POINTER(aURI);
01165 
01166     nsCAutoString newPath;
01167     nsresult rv = aURI->GetPath(newPath);
01168     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01169 
01170     // Append a forward slash if necessary
01171     PRInt32 len = newPath.Length();
01172     if (len > 0 && newPath.CharAt(len - 1) != '/')
01173     {
01174         newPath.Append('/');
01175     }
01176 
01177     // Store the path back on the URI
01178     AppendUTF16toUTF8(aPath, newPath);
01179     aURI->SetPath(newPath);
01180 
01181     return NS_OK;
01182 }
01183 
01184 nsresult nsWebBrowserPersist::SaveURIInternal(
01185     nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer,
01186     nsIInputStream *aPostData, const char *aExtraHeaders,
01187     nsIURI *aFile, PRBool aCalcFileExt)
01188 {
01189     NS_ENSURE_ARG_POINTER(aURI);
01190     NS_ENSURE_ARG_POINTER(aFile);
01191 
01192     nsresult rv = NS_OK;
01193     
01194     mURI = aURI;
01195 
01196     nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL;
01197     if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE)
01198     {
01199         loadFlags |= nsIRequest::LOAD_BYPASS_CACHE;
01200     }
01201     else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE)
01202     {
01203         loadFlags |= nsIRequest::LOAD_FROM_CACHE;
01204     }
01205 
01206     // Extract the cache key
01207     nsCOMPtr<nsISupports> cacheKey;
01208     if (aCacheKey)
01209     {
01210         // Test if the cache key is actually a web page descriptor (docshell)
01211         nsCOMPtr<nsIWebPageDescriptor> webPageDescriptor = do_QueryInterface(aCacheKey);
01212         if (webPageDescriptor)
01213         {
01214             nsCOMPtr<nsISupports> currentDescriptor;
01215             webPageDescriptor->GetCurrentDescriptor(getter_AddRefs(currentDescriptor));
01216             if (currentDescriptor)
01217             {
01218                 // Descriptor is actually a session history entry
01219                 nsCOMPtr<nsISHEntry> shEntry = do_QueryInterface(currentDescriptor);
01220                 NS_ASSERTION(shEntry, "The descriptor is meant to be a session history entry");
01221                 if (shEntry)
01222                 {
01223                     shEntry->GetCacheKey(getter_AddRefs(cacheKey));
01224                 }
01225             }
01226         }
01227         else
01228         {
01229             // Assume a plain cache key
01230             cacheKey = aCacheKey;
01231         }
01232     }
01233 
01234     // Open a channel to the URI
01235     nsCOMPtr<nsIChannel> inputChannel;
01236     rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI,
01237             nsnull, nsnull, NS_STATIC_CAST(nsIInterfaceRequestor *, this),
01238             loadFlags);
01239     
01240     if (NS_FAILED(rv) || inputChannel == nsnull)
01241     {
01242         EndDownload(NS_ERROR_FAILURE);
01243         return NS_ERROR_FAILURE;
01244     }
01245     
01246     // Disable content conversion
01247     if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)
01248     {
01249         nsCOMPtr<nsIEncodedChannel> encodedChannel(do_QueryInterface(inputChannel));
01250         if (encodedChannel)
01251         {
01252             encodedChannel->SetApplyConversion(PR_FALSE);
01253         }
01254     }
01255 
01256     // Set the referrer, post data and headers if any
01257     nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(inputChannel));
01258     if (httpChannel)
01259     {
01260         // Referrer
01261         if (aReferrer)
01262         {
01263             httpChannel->SetReferrer(aReferrer);
01264         }
01265 
01266         // Post data
01267         if (aPostData)
01268         {
01269             nsCOMPtr<nsISeekableStream> stream(do_QueryInterface(aPostData));
01270             if (stream)
01271             {
01272                 // Rewind the postdata stream
01273                 stream->Seek(nsISeekableStream::NS_SEEK_SET, 0);
01274                 nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel));
01275                 NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel");
01276                 // Attach the postdata to the http channel
01277                 uploadChannel->SetUploadStream(aPostData, EmptyCString(), -1);
01278             }
01279         }
01280 
01281         // Cache key
01282         nsCOMPtr<nsICachingChannel> cacheChannel(do_QueryInterface(httpChannel));
01283         if (cacheChannel && cacheKey)
01284         {
01285             cacheChannel->SetCacheKey(cacheKey);
01286         }
01287 
01288         // Headers
01289         if (aExtraHeaders)
01290         {
01291             nsCAutoString oneHeader;
01292             nsCAutoString headerName;
01293             nsCAutoString headerValue;
01294             PRInt32 crlf = 0;
01295             PRInt32 colon = 0;
01296             const char *kWhitespace = "\b\t\r\n ";
01297             nsCAutoString extraHeaders(aExtraHeaders);
01298             while (PR_TRUE)
01299             {
01300                 crlf = extraHeaders.Find("\r\n", PR_TRUE);
01301                 if (crlf == -1)
01302                     break;
01303                 extraHeaders.Mid(oneHeader, 0, crlf);
01304                 extraHeaders.Cut(0, crlf + 2);
01305                 colon = oneHeader.Find(":");
01306                 if (colon == -1)
01307                     break; // Should have a colon
01308                 oneHeader.Left(headerName, colon);
01309                 colon++;
01310                 oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon);
01311                 headerName.Trim(kWhitespace);
01312                 headerValue.Trim(kWhitespace);
01313                 // Add the header (merging if required)
01314                 rv = httpChannel->SetRequestHeader(headerName, headerValue, PR_TRUE);
01315                 if (NS_FAILED(rv))
01316                 {
01317                     EndDownload(NS_ERROR_FAILURE);
01318                     return NS_ERROR_FAILURE;
01319                 }
01320             }
01321         }
01322     }
01323     return SaveChannelInternal(inputChannel, aFile, aCalcFileExt);
01324 }
01325 
01326 nsresult nsWebBrowserPersist::SaveChannelInternal(
01327     nsIChannel *aChannel, nsIURI *aFile, PRBool aCalcFileExt)
01328 {
01329     NS_ENSURE_ARG_POINTER(aChannel);
01330     NS_ENSURE_ARG_POINTER(aFile);
01331 
01332     // Read from the input channel
01333     nsresult rv = aChannel->AsyncOpen(this, nsnull);
01334     if (rv == NS_ERROR_NO_CONTENT)
01335     {
01336         // Assume this is a protocol such as mailto: which does not feed out
01337         // data and just ignore it.
01338         return NS_SUCCESS_DONT_FIXUP;
01339     }
01340     else if (NS_FAILED(rv))
01341     {
01342         // Opening failed, but do we care?
01343         if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS)
01344         {
01345             EndDownload(NS_ERROR_FAILURE);
01346             return NS_ERROR_FAILURE;
01347         }
01348         return NS_SUCCESS_DONT_FIXUP;
01349     }
01350     else
01351     {
01352         // Add the output transport to the output map with the channel as the key
01353         nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aChannel);
01354         nsISupportsKey key(keyPtr);
01355         mOutputMap.Put(&key, new OutputData(aFile, mURI, aCalcFileExt));
01356     }
01357 
01358     return NS_OK;
01359 }
01360 
01361 nsresult
01362 nsWebBrowserPersist::GetExtensionForContentType(const PRUnichar *aContentType, PRUnichar **aExt)
01363 {
01364     NS_ENSURE_ARG_POINTER(aContentType);
01365     NS_ENSURE_ARG_POINTER(aExt);
01366 
01367     *aExt = nsnull;
01368 
01369     nsresult rv;
01370     if (!mMIMEService)
01371     {
01372         mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
01373         NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
01374     }
01375 
01376     nsCOMPtr<nsIMIMEInfo> mimeInfo;
01377     nsCAutoString contentType;
01378     contentType.AssignWithConversion(aContentType);
01379     nsCAutoString ext;
01380     rv = mMIMEService->GetPrimaryExtension(contentType, EmptyCString(), ext);
01381     if (NS_SUCCEEDED(rv))
01382     {
01383         *aExt = UTF8ToNewUnicode(ext);
01384         NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY);
01385         return NS_OK;
01386     }
01387 
01388     return NS_ERROR_FAILURE;
01389 }
01390 
01391 nsresult
01392 nsWebBrowserPersist::GetDocumentExtension(nsIDOMDocument *aDocument, PRUnichar **aExt)
01393 {
01394     NS_ENSURE_ARG_POINTER(aDocument);
01395     NS_ENSURE_ARG_POINTER(aExt);
01396 
01397     nsXPIDLString contentType;
01398     nsresult rv = GetDocEncoderContentType(aDocument, nsnull, getter_Copies(contentType));
01399     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01400     return GetExtensionForContentType(contentType.get(), aExt);
01401 }
01402 
01403 nsresult
01404 nsWebBrowserPersist::GetDocEncoderContentType(nsIDOMDocument *aDocument, const PRUnichar *aContentType, PRUnichar **aRealContentType)
01405 {
01406     NS_ENSURE_ARG_POINTER(aDocument);
01407     NS_ENSURE_ARG_POINTER(aRealContentType);
01408 
01409     *aRealContentType = nsnull;
01410 
01411     nsAutoString defaultContentType(NS_LITERAL_STRING("text/html"));
01412 
01413     // Get the desired content type for the document, either by using the one
01414     // supplied or from the document itself.
01415 
01416     nsAutoString contentType;
01417     if (aContentType)
01418     {
01419         contentType.Assign(aContentType);
01420     }
01421     else
01422     {
01423         // Get the content type from the document
01424         nsCOMPtr<nsIDOMNSDocument> nsDoc = do_QueryInterface(aDocument);
01425         if (nsDoc)
01426         {
01427             nsAutoString type;
01428             if (NS_SUCCEEDED(nsDoc->GetContentType(type)) && !type.IsEmpty())
01429             {
01430                 contentType.Assign(type);
01431             }
01432         }
01433     }
01434 
01435     // Check that an encoder actually exists for the desired output type. The
01436     // following content types will usually yield an encoder.
01437     //
01438     //   text/xml
01439     //   application/xml
01440     //   application/xhtml+xml
01441     //   image/svg+xml
01442     //   text/html
01443     //   text/plain
01444 
01445     if (!contentType.IsEmpty() &&
01446         !contentType.Equals(defaultContentType, nsCaseInsensitiveStringComparator()))
01447     {
01448         // Check if there is an encoder for the desired content type
01449         nsCAutoString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
01450         contractID.AppendWithConversion(contentType);
01451 
01452         nsCOMPtr<nsIComponentRegistrar> registrar;
01453         NS_GetComponentRegistrar(getter_AddRefs(registrar));
01454         if (registrar)
01455         {
01456             PRBool result;
01457             nsresult rv = registrar->IsContractIDRegistered(contractID.get(), &result);
01458             if (NS_SUCCEEDED(rv) && result)
01459             {
01460                 *aRealContentType = ToNewUnicode(contentType);
01461             }
01462         }
01463     }
01464 
01465     // Use the default if no encoder exists for the desired one
01466     if (!*aRealContentType)
01467     {
01468         *aRealContentType = ToNewUnicode(defaultContentType);
01469     }
01470     
01471     NS_ENSURE_TRUE(*aRealContentType, NS_ERROR_OUT_OF_MEMORY);
01472 
01473     return NS_OK;
01474 }
01475 
01476 nsresult nsWebBrowserPersist::SaveDocumentInternal(
01477     nsIDOMDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath)
01478 {
01479     NS_ENSURE_ARG_POINTER(aDocument);
01480     NS_ENSURE_ARG_POINTER(aFile);
01481 
01482     // See if we can get the local file representation of this URI
01483     nsCOMPtr<nsILocalFile> localFile;
01484     nsresult rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
01485 
01486     nsCOMPtr<nsILocalFile> localDataPath;
01487     if (NS_SUCCEEDED(rv) && aDataPath)
01488     {
01489         // See if we can get the local file representation of this URI
01490         rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath));
01491         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01492     }
01493 
01494     nsCOMPtr<nsIDOMNode> docAsNode = do_QueryInterface(aDocument);
01495 
01496     // Persist the main document
01497     nsCOMPtr<nsIDocument> doc(do_QueryInterface(aDocument));
01498     mURI = doc->GetDocumentURI();
01499 
01500     nsCOMPtr<nsIURI> oldBaseURI = mCurrentBaseURI;
01501     nsCAutoString oldCharset(mCurrentCharset);
01502 
01503     // Store the base URI and the charset
01504     mCurrentBaseURI = doc->GetBaseURI();
01505     mCurrentCharset = doc->GetDocumentCharacterSet();
01506 
01507     // Does the caller want to fixup the referenced URIs and save those too?
01508     if (aDataPath)
01509     {
01510         // Basic steps are these.
01511         //
01512         // 1. Iterate through the document (and subdocuments) building a list
01513         //    of unique URIs.
01514         // 2. For each URI create an OutputData entry and open a channel to save
01515         //    it. As each URI is saved, discover the mime type and fix up the
01516         //    local filename with the correct extension.
01517         // 3. Store the document in a list and wait for URI persistence to finish
01518         // 4. After URI persistence completes save the list of documents,
01519         //    fixing it up as it goes out to file.
01520 
01521         nsCOMPtr<nsIURI> oldDataPath = mCurrentDataPath;
01522         PRBool oldDataPathIsRelative = mCurrentDataPathIsRelative;
01523         nsCString oldCurrentRelativePathToData = mCurrentRelativePathToData;
01524         PRUint32 oldThingsToPersist = mCurrentThingsToPersist;
01525 
01526         mCurrentDataPathIsRelative = PR_FALSE;
01527         mCurrentDataPath = aDataPath;
01528         mCurrentRelativePathToData = "";
01529         mCurrentThingsToPersist = 0;
01530 
01531         // Determine if the specified data path is relative to the
01532         // specified file, (e.g. c:\docs\htmldata is relative to
01533         // c:\docs\myfile.htm, but not to d:\foo\data.
01534 
01535         // Starting with the data dir work back through its parents
01536         // checking if one of them matches the base directory.
01537 
01538         if (localDataPath && localFile)
01539         {
01540             nsCOMPtr<nsIFile> baseDir;
01541             localFile->GetParent(getter_AddRefs(baseDir));
01542 
01543             nsCAutoString relativePathToData;
01544             nsCOMPtr<nsIFile> dataDirParent;
01545             dataDirParent = localDataPath;
01546             while (dataDirParent)
01547             {
01548                 PRBool sameDir = PR_FALSE;
01549                 dataDirParent->Equals(baseDir, &sameDir);
01550                 if (sameDir)
01551                 {
01552                     mCurrentRelativePathToData = relativePathToData;
01553                     mCurrentDataPathIsRelative = PR_TRUE;
01554                     break;
01555                 }
01556 
01557                 nsAutoString dirName;
01558                 dataDirParent->GetLeafName(dirName);
01559 
01560                 nsCAutoString newRelativePathToData;
01561                 newRelativePathToData = NS_ConvertUTF16toUTF8(dirName)
01562                                       + NS_LITERAL_CSTRING("/")
01563                                       + relativePathToData;
01564                 relativePathToData = newRelativePathToData;
01565 
01566                 nsCOMPtr<nsIFile> newDataDirParent;
01567                 rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent));
01568                 dataDirParent = newDataDirParent;
01569             }
01570         }
01571         else
01572         {
01573             // generate a relative path if possible
01574             nsCOMPtr<nsIURL> pathToBaseURL(do_QueryInterface(aFile));
01575             if (pathToBaseURL)
01576             {
01577                 nsCAutoString relativePath;  // nsACString
01578                 if (NS_SUCCEEDED(pathToBaseURL->GetRelativeSpec(aDataPath, relativePath)))
01579                 {
01580                     mCurrentDataPathIsRelative = PR_TRUE;
01581                     mCurrentRelativePathToData = relativePath;
01582                 }
01583             }
01584         }
01585 
01586         // Store the document in a list so when URI persistence is done and the
01587         // filenames of saved URIs are known, the documents can be fixed up and
01588         // saved
01589 
01590         DocData *docData = new DocData;
01591         docData->mBaseURI = mCurrentBaseURI;
01592         docData->mCharset = mCurrentCharset;
01593         docData->mDocument = aDocument;
01594         docData->mFile = aFile;
01595         docData->mRelativePathToData = mCurrentRelativePathToData;
01596         docData->mDataPath = mCurrentDataPath;
01597         docData->mDataPathIsRelative = mCurrentDataPathIsRelative;
01598         mDocList.AppendElement(docData);
01599 
01600         // Walk the DOM gathering a list of externally referenced URIs in the uri map
01601         nsCOMPtr<nsIDOMDocumentTraversal> trav = do_QueryInterface(docData->mDocument, &rv);
01602         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01603         nsCOMPtr<nsIDOMTreeWalker> walker;
01604         rv = trav->CreateTreeWalker(docAsNode, 
01605             nsIDOMNodeFilter::SHOW_ELEMENT |
01606                 nsIDOMNodeFilter::SHOW_DOCUMENT |
01607                 nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION,
01608             nsnull, PR_TRUE, getter_AddRefs(walker));
01609         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01610 
01611         nsCOMPtr<nsIDOMNode> currentNode;
01612         walker->GetCurrentNode(getter_AddRefs(currentNode));
01613         while (currentNode)
01614         {
01615             OnWalkDOMNode(currentNode);
01616             walker->NextNode(getter_AddRefs(currentNode));
01617         }
01618 
01619         // If there are things to persist, create a directory to hold them
01620         if (mCurrentThingsToPersist > 0)
01621         {
01622             if (localDataPath)
01623             {
01624                 PRBool exists = PR_FALSE;
01625                 PRBool haveDir = PR_FALSE;
01626 
01627                 localDataPath->Exists(&exists);
01628                 if (exists)
01629                 {
01630                     localDataPath->IsDirectory(&haveDir);
01631                 }
01632                 if (!haveDir)
01633                 {
01634                     rv = localDataPath->Create(nsILocalFile::DIRECTORY_TYPE, 0755);
01635                     if (NS_SUCCEEDED(rv))
01636                         haveDir = PR_TRUE;
01637                     else
01638                         SendErrorStatusChange(PR_FALSE, rv, nsnull, aFile);
01639                 }
01640                 if (!haveDir)
01641                 {
01642                     EndDownload(NS_ERROR_FAILURE);
01643                     mCurrentBaseURI = oldBaseURI;
01644                     mCurrentCharset = oldCharset;
01645                     return NS_ERROR_FAILURE;
01646                 }
01647                 if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)
01648                 {
01649                     // Add to list of things to delete later if all goes wrong
01650                     CleanupData *cleanupData = new CleanupData;
01651                     NS_ENSURE_TRUE(cleanupData, NS_ERROR_OUT_OF_MEMORY);
01652                     cleanupData->mFile = localDataPath;
01653                     cleanupData->mIsDirectory = PR_TRUE;
01654                     mCleanupList.AppendElement(cleanupData);
01655                 }
01656 #if defined(XP_OS2)
01657                 // tag the directory with the URI that originated its contents
01658                 nsCOMPtr<nsILocalFileOS2> localFileOS2 = do_QueryInterface(localDataPath);
01659                 if (localFileOS2)
01660                 {
01661                     nsCAutoString url;
01662                     mCurrentBaseURI->GetSpec(url);
01663                     localFileOS2->SetFileSource(url);
01664                 }
01665 #endif
01666             }
01667         }
01668 
01669         mCurrentThingsToPersist = oldThingsToPersist;
01670         mCurrentDataPath = oldDataPath;
01671         mCurrentDataPathIsRelative = oldDataPathIsRelative;
01672         mCurrentRelativePathToData = oldCurrentRelativePathToData;
01673     }
01674     else
01675     {
01676         // Set the document base to ensure relative links still work
01677         SetDocumentBase(aDocument, mCurrentBaseURI);
01678 
01679         // Get the content type to save with
01680         nsXPIDLString realContentType;
01681         GetDocEncoderContentType(aDocument,
01682             !mContentType.IsEmpty() ? mContentType.get() : nsnull,
01683             getter_Copies(realContentType));
01684 
01685         nsCAutoString contentType; contentType.AssignWithConversion(realContentType);
01686         nsCAutoString charType; // Empty
01687 
01688         // Save the document
01689         nsCOMPtr<nsIDocument> docAsDoc = do_QueryInterface(aDocument);
01690         rv = SaveDocumentWithFixup(
01691             docAsDoc,
01692             nsnull,  // no dom fixup
01693             aFile,
01694             mReplaceExisting,
01695             contentType,
01696             charType,
01697             mEncodingFlags);
01698         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01699     }
01700 
01701     mCurrentBaseURI = oldBaseURI;
01702     mCurrentCharset = oldCharset;
01703 
01704     return NS_OK;
01705 }
01706 
01707 nsresult nsWebBrowserPersist::SaveDocuments()
01708 {
01709     nsresult rv = NS_OK;
01710 
01711     mStartSaving = PR_TRUE;
01712 
01713     // Iterate through all queued documents, saving them to file and fixing
01714     // them up on the way.
01715 
01716     PRInt32 i;
01717     for (i = 0; i < mDocList.Count(); i++)
01718     {
01719         DocData *docData = (DocData *) mDocList.ElementAt(i);
01720         if (!docData)
01721         {
01722             rv = NS_ERROR_FAILURE;
01723             break;
01724         }
01725 
01726         mCurrentBaseURI = docData->mBaseURI;
01727         mCurrentCharset = docData->mCharset;
01728 
01729         // Save the document, fixing it up with the new URIs as we do
01730         
01731         nsEncoderNodeFixup *nodeFixup;
01732         nodeFixup = new nsEncoderNodeFixup;
01733         if (nodeFixup)
01734             nodeFixup->mWebBrowserPersist = this;
01735 
01736         nsCOMPtr<nsIDocument> docAsDoc = do_QueryInterface(docData->mDocument);
01737 
01738         // Get the content type
01739         nsXPIDLString realContentType;
01740         GetDocEncoderContentType(docData->mDocument,
01741             !mContentType.IsEmpty() ? mContentType.get() : nsnull,
01742             getter_Copies(realContentType));
01743 
01744         nsCAutoString contentType; contentType.AssignWithConversion(realContentType.get());
01745         nsCAutoString charType; // Empty
01746 
01747         // Save the document, fixing up the links as it goes out
01748         rv = SaveDocumentWithFixup(
01749             docAsDoc,
01750             nodeFixup,
01751             docData->mFile,
01752             mReplaceExisting,
01753             contentType,
01754             charType,
01755             mEncodingFlags);
01756 
01757         if (NS_FAILED(rv))
01758             break;
01759 
01760         // if we're serializing, bail after first iteration of loop
01761         if (mSerializingOutput)
01762             break;
01763     }
01764 
01765     // delete, cleanup regardless of errors (bug 132417)
01766     for (i = 0; i < mDocList.Count(); i++)
01767     {
01768         DocData *docData = (DocData *) mDocList.ElementAt(i);
01769         delete docData;
01770         if (mSerializingOutput)
01771         {
01772             mDocList.RemoveElementAt(i);
01773             break;
01774         }
01775     }
01776 
01777     if (!mSerializingOutput)
01778     {
01779         mDocList.Clear();
01780     }
01781 
01782     return rv;
01783 }
01784 
01785 void nsWebBrowserPersist::Cleanup()
01786 {
01787     mURIMap.Enumerate(EnumCleanupURIMap, this);
01788     mURIMap.Reset();
01789     mOutputMap.Enumerate(EnumCleanupOutputMap, this);
01790     mOutputMap.Reset();
01791     mUploadList.Enumerate(EnumCleanupUploadList, this);
01792     mUploadList.Reset();
01793     PRInt32 i;
01794     for (i = 0; i < mDocList.Count(); i++)
01795     {
01796         DocData *docData = (DocData *) mDocList.ElementAt(i);
01797         delete docData;
01798     }
01799     mDocList.Clear();
01800     for (i = 0; i < mCleanupList.Count(); i++)
01801     {
01802         CleanupData *cleanupData = (CleanupData *) mCleanupList.ElementAt(i);
01803         delete cleanupData;
01804     }
01805     mCleanupList.Clear();
01806     mFilenameList.Clear();
01807 }
01808 
01809 void nsWebBrowserPersist::CleanupLocalFiles()
01810 {
01811     // Two passes, the first pass cleans up files, the second pass tests
01812     // for and then deletes empty directories. Directories that are not
01813     // empty after the first pass must contain files from something else
01814     // and are not deleted.
01815     int pass;
01816     for (pass = 0; pass < 2; pass++)
01817     {
01818         PRInt32 i;
01819         for (i = 0; i < mCleanupList.Count(); i++)
01820         {
01821             CleanupData *cleanupData = (CleanupData *) mCleanupList.ElementAt(i);
01822             nsCOMPtr<nsILocalFile> file = cleanupData->mFile;
01823 
01824             // Test if the dir / file exists (something in an earlier loop
01825             // may have already removed it)
01826             PRBool exists = PR_FALSE;
01827             file->Exists(&exists);
01828             if (!exists)
01829                 continue;
01830 
01831             // Test if the file has changed in between creation and deletion
01832             // in some way that means it should be ignored
01833             PRBool isDirectory = PR_FALSE;
01834             file->IsDirectory(&isDirectory);
01835             if (isDirectory != cleanupData->mIsDirectory)
01836                 continue; // A file has become a dir or vice versa !
01837 
01838             if (pass == 0 && !isDirectory)
01839             {
01840                 file->Remove(PR_FALSE);
01841             }
01842             else if (pass == 1 && isDirectory) // Directory
01843             {
01844                 // Directories are more complicated. Enumerate through
01845                 // children looking for files. Any files created by the
01846                 // persist object would have been deleted by the first
01847                 // pass so if there are any there at this stage, the dir
01848                 // cannot be deleted because it has someone else's files
01849                 // in it. Empty child dirs are deleted but they must be
01850                 // recursed through to ensure they are actually empty.
01851 
01852                 PRBool isEmptyDirectory = PR_TRUE;
01853                 nsSupportsArray dirStack;
01854                 PRUint32 stackSize = 0;
01855 
01856                 // Push the top level enum onto the stack
01857                 nsCOMPtr<nsISimpleEnumerator> pos;
01858                 if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos))))
01859                     dirStack.AppendElement(pos);
01860 
01861                 while (isEmptyDirectory &&
01862                     NS_SUCCEEDED(dirStack.Count(&stackSize)) && stackSize > 0)
01863                 {
01864                     // Pop the last element
01865                     nsCOMPtr<nsISimpleEnumerator> curPos;
01866                     dirStack.GetElementAt(stackSize - 1, getter_AddRefs(curPos));
01867                     dirStack.RemoveElementAt(stackSize - 1);
01868                     
01869                     // Test if the enumerator has any more files in it
01870                     PRBool hasMoreElements = PR_FALSE;
01871                     curPos->HasMoreElements(&hasMoreElements);
01872                     if (!hasMoreElements)
01873                     {
01874                         continue;
01875                     }
01876 
01877                     // Child files automatically make this code drop out,
01878                     // while child dirs keep the loop going.
01879                     nsCOMPtr<nsISupports> child;
01880                     curPos->GetNext(getter_AddRefs(child));
01881                     NS_ASSERTION(child, "No child element, but hasMoreElements says otherwise");
01882                     if (!child)
01883                         continue;
01884                     nsCOMPtr<nsILocalFile> childAsFile = do_QueryInterface(child);
01885                     NS_ASSERTION(childAsFile, "This should be a file but isn't");
01886 
01887                     PRBool childIsSymlink = PR_FALSE;
01888                     childAsFile->IsSymlink(&childIsSymlink);
01889                     PRBool childIsDir = PR_FALSE;
01890                     childAsFile->IsDirectory(&childIsDir);                           
01891                     if (!childIsDir || childIsSymlink)
01892                     {
01893                         // Some kind of file or symlink which means dir
01894                         // is not empty so just drop out.
01895                         isEmptyDirectory = PR_FALSE;
01896                         break;
01897                     }
01898                     // Push parent enumerator followed by child enumerator
01899                     nsCOMPtr<nsISimpleEnumerator> childPos;
01900                     childAsFile->GetDirectoryEntries(getter_AddRefs(childPos));
01901                     dirStack.AppendElement(curPos);
01902                     if (childPos)
01903                         dirStack.AppendElement(childPos);
01904 
01905                 }
01906                 dirStack.Clear();
01907 
01908                 // If after all that walking the dir is deemed empty, delete it
01909                 if (isEmptyDirectory)
01910                 {
01911                     file->Remove(PR_TRUE);
01912                 }
01913             }
01914         }
01915     }
01916 }
01917 
01918 nsresult
01919 nsWebBrowserPersist::CalculateUniqueFilename(nsIURI *aURI)
01920 {
01921     nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
01922     NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
01923 
01924     PRBool nameHasChanged = PR_FALSE;
01925     nsresult rv;
01926 
01927     // Get the old filename
01928     nsCAutoString filename;
01929     rv = url->GetFileName(filename);
01930     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01931     nsCAutoString directory;
01932     rv = url->GetDirectory(directory);
01933     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
01934 
01935     // Split the filename into a base and an extension.
01936     // e.g. "foo.html" becomes "foo" & ".html"
01937     //
01938     // The nsIURL methods GetFileBaseName & GetFileExtension don't
01939     // preserve the dot whereas this code does to save some effort
01940     // later when everything is put back together.
01941     PRInt32 lastDot = filename.RFind(".");
01942     nsCAutoString base;
01943     nsCAutoString ext;
01944     if (lastDot >= 0)
01945     {
01946         filename.Mid(base, 0, lastDot);
01947         filename.Mid(ext, lastDot, filename.Length() - lastDot); // includes dot
01948     }
01949     else
01950     {
01951         // filename contains no dot
01952         base = filename;
01953     }
01954 
01955     // Test if the filename is longer than allowed by the OS
01956     PRInt32 needToChop = filename.Length() - kDefaultMaxFilenameLength;
01957     if (needToChop > 0)
01958     {
01959         // Truncate the base first and then the ext if necessary
01960         if (base.Length() > (PRUint32) needToChop)
01961         {
01962             base.Truncate(base.Length() - needToChop);
01963         }
01964         else
01965         {
01966             needToChop -= base.Length() - 1;
01967             base.Truncate(1);
01968             if (ext.Length() > (PRUint32) needToChop)
01969             {
01970                 ext.Truncate(ext.Length() - needToChop);
01971             }
01972             else
01973             {
01974                 ext.Truncate(0);
01975             }
01976             // If kDefaultMaxFilenameLength were 1 we'd be in trouble here,
01977             // but that won't happen because it will be set to a sensible
01978             // value.
01979         }
01980 
01981         filename.Assign(base);
01982         filename.Append(ext);
01983         nameHasChanged = PR_TRUE;
01984     }
01985 
01986     // Ensure the filename is unique
01987     // Create a filename if it's empty, or if the filename / datapath is
01988     // already taken by another URI and create an alternate name.
01989 
01990     if (base.IsEmpty() || mFilenameList.Count() > 0)
01991     {
01992         nsCAutoString tmpPath;
01993         nsCAutoString tmpBase;
01994         PRUint32 duplicateCounter = 1;
01995         while (1)
01996         {
01997             // Make a file name,
01998             // Foo become foo_001, foo_002, etc.
01999             // Empty files become _001, _002 etc.
02000 
02001             if (base.IsEmpty() || duplicateCounter > 1)
02002             {
02003                 char * tmp = PR_smprintf("_%03d", duplicateCounter);
02004                 NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY);
02005                 if (filename.Length() < kDefaultMaxFilenameLength - 4)
02006                 {
02007                     tmpBase = base;
02008                 }
02009                 else
02010                 {
02011                     base.Mid(tmpBase, 0, base.Length() - 4);
02012                 }
02013                 tmpBase.Append(tmp);
02014                 PR_smprintf_free(tmp);
02015             }
02016             else
02017             {
02018                 tmpBase = base;
02019             }
02020         
02021             tmpPath.Assign(directory);
02022             tmpPath.Append(tmpBase);
02023             tmpPath.Append(ext);
02024 
02025             // Test if the name is a duplicate
02026             if (mFilenameList.IndexOf(tmpPath) < 0)
02027             {
02028                 if (!base.Equals(tmpBase))
02029                 {
02030                     filename.Assign(tmpBase);
02031                     filename.Append(ext);
02032                     nameHasChanged = PR_TRUE;
02033                 }
02034                 break;
02035             }
02036             duplicateCounter++;
02037         }
02038     }
02039 
02040     // Add name to list of those already used
02041     nsCAutoString newFilepath(directory);
02042     newFilepath.Append(filename);
02043     mFilenameList.AppendCString(newFilepath);
02044 
02045     // Update the uri accordingly if the filename actually changed
02046     if (nameHasChanged)
02047     {
02048         // Final sanity test
02049         if (filename.Length() > kDefaultMaxFilenameLength)
02050         {
02051             NS_WARNING("Filename wasn't truncated less than the max file length - how can that be?");
02052             return NS_ERROR_FAILURE;
02053         }
02054 
02055         nsCOMPtr<nsILocalFile> localFile;
02056         GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
02057 
02058         if (localFile)
02059         {
02060             nsAutoString filenameAsUnichar;
02061             filenameAsUnichar.AssignWithConversion(filename.get());
02062             localFile->SetLeafName(filenameAsUnichar);
02063 
02064             // Resync the URI with the file after the extension has been appended
02065             nsresult rv;
02066             nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
02067             NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
02068             fileURL->SetFile(localFile);  // this should recalculate uri
02069         }
02070         else
02071         {
02072             url->SetFileName(filename);
02073         }
02074     }
02075 
02076     return NS_OK;
02077 }
02078 
02079 
02080 nsresult
02081 nsWebBrowserPersist::MakeFilenameFromURI(nsIURI *aURI, nsString &aFilename)
02082 {
02083     // Try to get filename from the URI.
02084     nsAutoString fileName;
02085 
02086     // Get a suggested file name from the URL but strip it of characters
02087     // likely to cause the name to be illegal.
02088 
02089     nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
02090     if (url)
02091     {
02092         nsCAutoString nameFromURL;
02093         url->GetFileName(nameFromURL);
02094         if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)
02095         {
02096             fileName.AssignWithConversion(NS_UnescapeURL(nameFromURL).get());
02097             goto end;
02098         }
02099         if (!nameFromURL.IsEmpty())
02100         {
02101             // Unescape the file name (GetFileName escapes it)
02102             NS_UnescapeURL(nameFromURL);
02103             PRUint32 nameLength = 0;
02104             const char *p = nameFromURL.get();
02105             for (;*p && *p != ';' && *p != '?' && *p != '#' && *p != '.'
02106                  ;p++)
02107             {
02108                 if (nsCRT::IsAsciiAlpha(*p) || nsCRT::IsAsciiDigit(*p)
02109                     || *p == '.' || *p == '-' ||  *p == '_' || (*p == ' '))
02110                 {
02111                     fileName.Append(PRUnichar(*p));
02112                     if (++nameLength == kDefaultMaxFilenameLength)
02113                     {
02114                         // Note:
02115                         // There is no point going any further since it will be
02116                         // truncated in CalculateUniqueFilename anyway.
02117                         // More importantly, certain implementations of
02118                         // nsILocalFile (e.g. the Mac impl) might truncate
02119                         // names in undesirable ways, such as truncating from
02120                         // the middle, inserting ellipsis and so on.
02121                         break;
02122                     }
02123                 }
02124             }
02125         }
02126     }
02127 
02128     // Empty filenames can confuse the local file object later 
02129     // when it attempts to set the leaf name in CalculateUniqueFilename
02130     // for duplicates and ends up replacing the parent dir. To avoid
02131     // the problem, all filenames are made at least one character long.
02132     if (fileName.IsEmpty())
02133     {
02134         fileName.Append(PRUnichar('a')); // 'a' is for arbitrary
02135     }
02136  
02137 end:
02138     aFilename = fileName;
02139     return NS_OK;
02140 }
02141 
02142 
02143 nsresult
02144 nsWebBrowserPersist::CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, nsIURI *aOriginalURIWithExtension)
02145 {
02146     nsresult rv;
02147 
02148     if (!mMIMEService)
02149     {
02150         mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
02151         NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE);
02152     }
02153 
02154     nsCAutoString contentType;
02155 
02156     // Get the content type from the channel
02157     aChannel->GetContentType(contentType);
02158 
02159     // Get the content type from the MIME service
02160     if (contentType.IsEmpty())
02161     {
02162         nsCOMPtr<nsIURI> uri;
02163         aChannel->GetOriginalURI(getter_AddRefs(uri));
02164         mMIMEService->GetTypeFromURI(uri, contentType);
02165     }
02166 
02167     // Append the extension onto the file
02168     if (!contentType.IsEmpty())
02169     {
02170         nsCOMPtr<nsIMIMEInfo> mimeInfo;
02171         mMIMEService->GetFromTypeAndExtension(
02172             contentType, EmptyCString(), getter_AddRefs(mimeInfo));
02173 
02174         nsCOMPtr<nsILocalFile> localFile;
02175         GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
02176 
02177         if (mimeInfo)
02178         {
02179             nsCOMPtr<nsIURL> url(do_QueryInterface(aURI));
02180             NS_ENSURE_TRUE(url, NS_ERROR_FAILURE);
02181 
02182             nsCAutoString newFileName;
02183             url->GetFileName(newFileName);
02184 
02185             // Test if the current extension is current for the mime type
02186             PRBool hasExtension = PR_FALSE;
02187             PRInt32 ext = newFileName.RFind(".");
02188             if (ext != -1)
02189             {
02190                 mimeInfo->ExtensionExists(Substring(newFileName, ext + 1), &hasExtension);
02191             }
02192 
02193             // Append the mime file extension
02194             nsCAutoString fileExt;
02195             if (!hasExtension)
02196             {
02197                 // Test if previous extension is acceptable
02198                 nsCOMPtr<nsIURL> oldurl(do_QueryInterface(aOriginalURIWithExtension));
02199                 NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE);
02200                 oldurl->GetFileExtension(fileExt);
02201                 PRBool useOldExt = PR_FALSE;
02202                 if (!fileExt.IsEmpty())
02203                 {
02204                     mimeInfo->ExtensionExists(fileExt, &useOldExt);
02205                 }
02206 
02207                 // can't use old extension so use primary extension
02208                 if (!useOldExt)
02209                 {
02210                     mimeInfo->GetPrimaryExtension(fileExt);
02211                 } 
02212 
02213                 if (!fileExt.IsEmpty())
02214                 {
02215                     PRUint32 newLength = newFileName.Length() + fileExt.Length() + 1;
02216                     if (newLength > kDefaultMaxFilenameLength)
02217                     {
02218                         newFileName.Truncate(newFileName.Length() - (newLength - kDefaultMaxFilenameLength));
02219                     }
02220                     newFileName.Append(".");
02221                     newFileName.Append(fileExt);
02222                 }
02223 
02224                 if (localFile)
02225                 {
02226                     localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName));
02227 
02228                     // Resync the URI with the file after the extension has been appended
02229                     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(aURI, &rv);
02230                     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
02231                     fileURL->SetFile(localFile);  // this should recalculate uri
02232                 }
02233                 else
02234                 {
02235                     url->SetFileName(newFileName);
02236                 }
02237             }
02238 
02239         }
02240 
02241 #ifdef  XP_MAC
02242         // Set appropriate Mac file type/creator for this mime type
02243         nsCOMPtr<nsILocalFileMac> macFile(do_QueryInterface(localFile));
02244         if (macFile)
02245         {
02246             macFile->SetFileTypeAndCreatorFromMIMEType(contentType.get());
02247         }
02248 #endif            
02249     }
02250 
02251     return NS_OK;
02252 }
02253 
02254 nsresult
02255 nsWebBrowserPersist::MakeOutputStream(
02256     nsIURI *aURI, nsIOutputStream **aOutputStream)
02257 {
02258     nsresult rv;
02259 
02260     nsCOMPtr<nsILocalFile> localFile;
02261     GetLocalFileFromURI(aURI, getter_AddRefs(localFile));
02262     if (localFile)
02263         rv = MakeOutputStreamFromFile(localFile, aOutputStream);
02264     else
02265         rv = MakeOutputStreamFromURI(aURI, aOutputStream);
02266 
02267     return rv;
02268 }
02269 
02270 nsresult
02271 nsWebBrowserPersist::MakeOutputStreamFromFile(
02272     nsILocalFile *aFile, nsIOutputStream **aOutputStream)
02273 {
02274     nsresult rv = NS_OK;
02275 
02276     nsCOMPtr<nsIFileOutputStream> fileOutputStream =
02277         do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv);
02278     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
02279 
02280     // XXX brade:  get the right flags here!
02281     rv = fileOutputStream->Init(aFile, -1, -1, 0);
02282     NS_ENSURE_SUCCESS(rv, rv);
02283 
02284     NS_ENSURE_SUCCESS(CallQueryInterface(fileOutputStream, aOutputStream), NS_ERROR_FAILURE);
02285 
02286     if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)
02287     {
02288         // Add to cleanup list in event of failure
02289         CleanupData *cleanupData = new CleanupData;
02290         NS_ENSURE_TRUE(cleanupData, NS_ERROR_OUT_OF_MEMORY);
02291         cleanupData->mFile = aFile;
02292         cleanupData->mIsDirectory = PR_FALSE;
02293         mCleanupList.AppendElement(cleanupData);
02294     }
02295 
02296     return NS_OK;
02297 }
02298 
02299 nsresult
02300 nsWebBrowserPersist::MakeOutputStreamFromURI(
02301     nsIURI *aURI, nsIOutputStream  **aOutputStream)
02302 {
02303     PRUint32 segsize = 8192;
02304     PRUint32 maxsize = PRUint32(-1);
02305     nsCOMPtr<nsIStorageStream> storStream;
02306     nsresult rv = NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream));
02307     NS_ENSURE_SUCCESS(rv, rv);
02308     
02309     NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream), NS_ERROR_FAILURE);
02310     return NS_OK;
02311 }
02312 
02313 void
02314 nsWebBrowserPersist::EndDownload(nsresult aResult)
02315 {
02316     // Store the error code in the result if it is an error
02317     if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult))
02318     {
02319         mPersistResult = aResult;
02320     }
02321 
02322     // Do file cleanup if required
02323     if (NS_FAILED(aResult) && (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE))
02324     {
02325         CleanupLocalFiles();
02326     }
02327 
02328     // Cleanup the channels
02329     mCompleted = PR_TRUE;
02330     Cleanup();
02331 }
02332 
02333 /* Hack class to get access to nsISupportsKey's protected mKey member */
02334 class nsMyISupportsKey : public nsISupportsKey
02335 {
02336 public:
02337     nsMyISupportsKey(nsISupports *key) : nsISupportsKey(key)
02338     {
02339     }
02340 
02341     nsresult GetISupports(nsISupports **ret)
02342     {
02343         *ret = mKey;
02344         NS_IF_ADDREF(mKey);
02345         return NS_OK;
02346     }
02347 };
02348 
02349 struct FixRedirectData
02350 {
02351     nsCOMPtr<nsIChannel> mNewChannel;
02352     nsCOMPtr<nsIURI> mOriginalURI;
02353     nsISupportsKey *mMatchingKey;
02354 };
02355 
02356 nsresult
02357 nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel)
02358 {
02359     NS_ENSURE_ARG_POINTER(aNewChannel);
02360     nsCOMPtr<nsIURI> originalURI;
02361 
02362     // Enumerate through existing open channels looking for one with
02363     // a URI matching the one specified.
02364 
02365     FixRedirectData data;
02366     data.mMatchingKey = nsnull;
02367     data.mNewChannel = aNewChannel;
02368     data.mNewChannel->GetOriginalURI(getter_AddRefs(data.mOriginalURI));
02369     mOutputMap.Enumerate(EnumFixRedirect, (void *) &data);
02370 
02371     // If a match is found, remove the data entry with the old channel key
02372     // and re-add it with the new channel key.
02373 
02374     if (data.mMatchingKey)
02375     {
02376         OutputData *outputData = (OutputData *) mOutputMap.Get(data.mMatchingKey);
02377         NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE);
02378         mOutputMap.Remove(data.mMatchingKey);
02379 
02380         // Store data again with new channel unless told to ignore redirects
02381         if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA))
02382         {
02383             nsCOMPtr<nsISupports> keyPtr = do_QueryInterface(aNewChannel);
02384             nsISupportsKey key(keyPtr);
02385             mOutputMap.Put(&key, outputData);
02386         }
02387     }
02388 
02389     return NS_OK;
02390 }
02391 
02392 PRBool PR_CALLBACK
02393 nsWebBrowserPersist::EnumFixRedirect(nsHashKey *aKey, void *aData, void* closure)
02394 {
02395     FixRedirectData *data = (FixRedirectData *) closure;
02396 
02397     nsCOMPtr<nsISupports> keyPtr;
02398     ((nsMyISupportsKey *) aKey)->GetISupports(getter_AddRefs(keyPtr));
02399 
02400     nsCOMPtr<nsIChannel> thisChannel = do_QueryInterface(keyPtr);
02401     nsCOMPtr<nsIURI> thisURI;
02402 
02403     thisChannel->GetOriginalURI(getter_AddRefs(thisURI));
02404 
02405     // Compare this channel's URI to the one passed in.
02406     PRBool matchingURI = PR_FALSE;
02407     thisURI->Equals(data->mOriginalURI, &matchingURI);
02408     if (matchingURI)
02409     {
02410         data->mMatchingKey = (nsISupportsKey *) aKey;
02411         return PR_FALSE; // Stop enumerating
02412     }
02413 
02414     return PR_TRUE;
02415 }
02416 
02417 void
02418 nsWebBrowserPersist::CalcTotalProgress()
02419 {
02420     mTotalCurrentProgress = 0;
02421     mTotalMaxProgress = 0;
02422 
02423     if (mOutputMap.Count() > 0)
02424     {
02425         // Total up the progress of each output stream
02426         mOutputMap.Enumerate(EnumCalcProgress, this);
02427     }
02428 
02429     if (mUploadList.Count() > 0)
02430     {
02431         // Total up the progress of each upload
02432         mUploadList.Enumerate(EnumCalcUploadProgress, this);
02433     }
02434 
02435     // XXX this code seems pretty bogus and pointless
02436     if (mTotalCurrentProgress == LL_ZERO && mTotalMaxProgress == LL_ZERO)
02437     {
02438         // No output streams so we must be complete
02439         mTotalCurrentProgress = 10000;
02440         mTotalMaxProgress = 10000;
02441     }
02442 }
02443 
02444 PRBool PR_CALLBACK
02445 nsWebBrowserPersist::EnumCalcProgress(nsHashKey *aKey, void *aData, void* closure)
02446 {
02447     nsWebBrowserPersist *pthis = (nsWebBrowserPersist *) closure;
02448     OutputData *data = (OutputData *) aData;
02449 
02450     // only count toward total progress if destination file is local
02451     nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(data->mFile);
02452     if (fileURL)
02453     {
02454         pthis->mTotalCurrentProgress += data->mSelfProgress;
02455         pthis->mTotalMaxProgress += data->mSelfProgressMax;
02456     }
02457     return PR_TRUE;
02458 }
02459 
02460 PRBool PR_CALLBACK
02461 nsWebBrowserPersist::EnumCalcUploadProgress(nsHashKey *aKey, void *aData, void* closure)
02462 {
02463     if (aData && closure)
02464     {
02465         nsWebBrowserPersist *pthis = (nsWebBrowserPersist *) closure;
02466         UploadData *data = (UploadData *) aData;
02467         pthis->mTotalCurrentProgress += data->mSelfProgress;
02468         pthis->mTotalMaxProgress += data->mSelfProgressMax;
02469     }
02470     return PR_TRUE;
02471 }
02472 
02473 PRBool PR_CALLBACK
02474 nsWebBrowserPersist::EnumCountURIsToPersist(nsHashKey *aKey, void *aData, void* closure)
02475 {
02476     URIData *data = (URIData *) aData;
02477     PRUint32 *count = (PRUint32 *) closure;
02478     if (data->mNeedsPersisting && !data->mSaved)
02479     {
02480         (*count)++;
02481     }
02482     return PR_TRUE;
02483 }
02484 
02485 PRBool PR_CALLBACK
02486 nsWebBrowserPersist::EnumPersistURIs(nsHashKey *aKey, void *aData, void* closure)
02487 {
02488     URIData *data = (URIData *) aData;
02489     if (!data->mNeedsPersisting || data->mSaved)
02490     {
02491         return PR_TRUE;
02492     }
02493 
02494     nsWebBrowserPersist *pthis = (nsWebBrowserPersist *) closure;
02495     nsresult rv;
02496 
02497     // Create a URI from the key
02498     nsCOMPtr<nsIURI> uri;
02499     rv = NS_NewURI(getter_AddRefs(uri), 
02500                    nsDependentCString(((nsCStringKey *) aKey)->GetString(),
02501                                       ((nsCStringKey *) aKey)->GetStringLength()),
02502                    data->mCharset.get());
02503     NS_ENSURE_SUCCESS(rv, PR_FALSE);
02504 
02505     // Make a URI to save the data to
02506     nsCOMPtr<nsIURI> fileAsURI;
02507     rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI));
02508     NS_ENSURE_SUCCESS(rv, PR_FALSE);
02509     rv = pthis->AppendPathToURI(fileAsURI, data->mFilename);
02510     NS_ENSURE_SUCCESS(rv, PR_FALSE);
02511 
02512     rv = pthis->SaveURIInternal(uri, nsnull, nsnull, nsnull, nsnull, fileAsURI, PR_TRUE);
02513     // if SaveURIInternal fails, then it will have called EndDownload,
02514     // which means that |aData| is no longer valid memory.  we MUST bail.
02515     NS_ENSURE_SUCCESS(rv, PR_FALSE);
02516 
02517     if (rv == NS_OK)
02518     {
02519         // Store the actual object because once it's persisted this
02520         // will be fixed up with the right file extension.
02521 
02522         data->mFile = fileAsURI;
02523         data->mSaved = PR_TRUE;
02524     }
02525     else
02526     {
02527         data->mNeedsFixup = PR_FALSE;
02528     }
02529 
02530     if (pthis->mSerializingOutput)
02531         return PR_FALSE;
02532 
02533     return PR_TRUE;
02534 }
02535 
02536 PRBool PR_CALLBACK
02537 nsWebBrowserPersist::EnumCleanupOutputMap(nsHashKey *aKey, void *aData, void* closure)
02538 {
02539     nsCOMPtr<nsISupports> keyPtr;
02540     ((nsMyISupportsKey *) aKey)->GetISupports(getter_AddRefs(keyPtr));
02541     nsCOMPtr<nsIChannel> channel = do_QueryInterface(keyPtr);
02542     if (channel)
02543     {
02544         channel->Cancel(NS_BINDING_ABORTED);
02545     }
02546     OutputData *data = (OutputData *) aData;
02547     if (data)
02548     {
02549         delete data;
02550     }
02551     return PR_TRUE;
02552 }
02553 
02554 
02555 PRBool PR_CALLBACK
02556 nsWebBrowserPersist::EnumCleanupURIMap(nsHashKey *aKey, void *aData, void* closure)
02557 {
02558     URIData *data = (URIData *) aData;
02559     if (data)
02560     {
02561         delete data; // Delete data associated with key
02562     }
02563     return PR_TRUE;
02564 }
02565 
02566 
02567 PRBool PR_CALLBACK
02568 nsWebBrowserPersist::EnumCleanupUploadList(nsHashKey *aKey, void *aData, void* closure)
02569 {
02570     nsCOMPtr<nsISupports> keyPtr;
02571     ((nsMyISupportsKey *) aKey)->GetISupports(getter_AddRefs(keyPtr));
02572     nsCOMPtr<nsIChannel> channel = do_QueryInterface(keyPtr);
02573     if (channel)
02574     {
02575         channel->Cancel(NS_BINDING_ABORTED);
02576     }
02577     UploadData *data = (UploadData *) aData;
02578     if (data)
02579     {
02580         delete data; // Delete data associated with key
02581     }
02582     return PR_TRUE;
02583 }
02584 
02585 
02586 PRBool
02587 nsWebBrowserPersist::GetQuotedAttributeValue(
02588     const nsAString &aSource, const nsAString &aAttribute, nsAString &aValue)
02589 {  
02590     // NOTE: This code was lifted verbatim from nsParserUtils.cpp
02591     aValue.Truncate();
02592     nsAString::const_iterator start, end;
02593     aSource.BeginReading(start);
02594     aSource.EndReading(end);
02595     nsAString::const_iterator iter(end);
02596 
02597     while (start != end) {
02598         if (FindInReadable(aAttribute, start, iter))
02599         {
02600             // walk past any whitespace
02601             while (iter != end && nsCRT::IsAsciiSpace(*iter))
02602             {
02603                 ++iter;
02604             }
02605 
02606             if (iter == end)
02607                 break;
02608             
02609             // valid name="value" pair?
02610             if (*iter != '=')
02611             {
02612                 start = iter;
02613                 iter = end;
02614                 continue;
02615             }
02616             // move past the =
02617             ++iter;
02618 
02619             while (iter != end && nsCRT::IsAsciiSpace(*iter))
02620             {
02621                 ++iter;
02622             }
02623 
02624             if (iter == end)
02625                 break;
02626 
02627             PRUnichar q = *iter;
02628             if (q != '"' && q != '\'')
02629             {
02630                 start = iter;
02631                 iter = end;
02632                 continue;
02633             }
02634 
02635             // point to the first char of the value
02636             ++iter;
02637             start = iter;
02638             if (FindCharInReadable(q, iter, end))
02639             {
02640                 aValue = Substring(start, iter);
02641                 return PR_TRUE;
02642             }
02643 
02644             // we've run out of string.  Just return...
02645             break;
02646          }
02647     }
02648     return PR_FALSE;
02649 }
02650 
02651 nsresult nsWebBrowserPersist::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, const nsAString &aHref)
02652 {
02653     NS_ENSURE_ARG_POINTER(aPI);
02654     nsresult rv = NS_OK;
02655 
02656     nsAutoString data;
02657     rv = aPI->GetData(data);
02658     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
02659 
02660     nsAutoString href;
02661     GetQuotedAttributeValue(data, NS_LITERAL_STRING("href"), href);
02662 
02663     // Construct and set a new data value for the xml-stylesheet
02664     if (!aHref.IsEmpty() && !href.IsEmpty())
02665     {
02666         nsAutoString alternate;
02667         nsAutoString charset;
02668         nsAutoString title;
02669         nsAutoString type;
02670         nsAutoString media;
02671 
02672         GetQuotedAttributeValue(data, NS_LITERAL_STRING("alternate"), alternate);
02673         GetQuotedAttributeValue(data, NS_LITERAL_STRING("charset"), charset);
02674         GetQuotedAttributeValue(data, NS_LITERAL_STRING("title"), title);
02675         GetQuotedAttributeValue(data, NS_LITERAL_STRING("type"), type);
02676         GetQuotedAttributeValue(data, NS_LITERAL_STRING("media"), media);
02677 
02678         NS_NAMED_LITERAL_STRING(kCloseAttr, "\" ");
02679         nsAutoString newData;
02680         newData += NS_LITERAL_STRING("href=\"") + aHref + kCloseAttr;
02681         if (!title.IsEmpty())
02682         {
02683             newData += NS_LITERAL_STRING("title=\"") + title + kCloseAttr;
02684         }
02685         if (!media.IsEmpty())
02686         {
02687             newData += NS_LITERAL_STRING("media=\"") + media + kCloseAttr;
02688         }
02689         if (!type.IsEmpty())
02690         {
02691             newData += NS_LITERAL_STRING("type=\"") + type + kCloseAttr;
02692         }
02693         if (!charset.IsEmpty())
02694         {
02695             newData += NS_LITERAL_STRING("charset=\"") + charset + kCloseAttr;
02696         }
02697         if (!alternate.IsEmpty())
02698         {
02699             newData += NS_LITERAL_STRING("alternate=\"") + alternate + kCloseAttr;
02700         }
02701         newData.Truncate(newData.Length() - 1);  // Remove the extra space on the end.
02702         aPI->SetData(newData);
02703     }
02704 
02705     return rv;
02706 }
02707 
02708 nsresult nsWebBrowserPersist::GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref)
02709 {
02710     NS_ENSURE_ARG_POINTER(aPI);
02711 
02712     nsresult rv = NS_OK;
02713     nsAutoString data;
02714     rv = aPI->GetData(data);
02715     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
02716 
02717     GetQuotedAttributeValue(data, NS_LITERAL_STRING("href"), aHref);
02718 
02719     return NS_OK;
02720 }
02721 
02722 nsresult nsWebBrowserPersist::OnWalkDOMNode(nsIDOMNode *aNode)
02723 {
02724     // Fixup xml-stylesheet processing instructions
02725     nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNode);
02726     if (nodeAsPI)
02727     {
02728         nsAutoString target;
02729         nodeAsPI->GetTarget(target);
02730         if (target.EqualsLiteral("xml-stylesheet"))
02731         {
02732             nsAutoString href;
02733             GetXMLStyleSheetLink(nodeAsPI, href);
02734             if (!href.IsEmpty())
02735             {
02736                 StoreURI(NS_ConvertUCS2toUTF8(href).get());
02737             }
02738         }
02739     }
02740 
02741     // Test the node to see if it's an image, frame, iframe, css, js
02742     nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNode);
02743     if (nodeAsImage)
02744     {
02745         StoreURIAttribute(aNode, "src");
02746         return NS_OK;
02747     }
02748 
02749     nsCOMPtr<nsIDOMHTMLBodyElement> nodeAsBody = do_QueryInterface(aNode);
02750     if (nodeAsBody)
02751     {
02752         StoreURIAttribute(aNode, "background");
02753         return NS_OK;
02754     }
02755 
02756     nsCOMPtr<nsIDOMHTMLTableElement> nodeAsTable = do_QueryInterface(aNode);
02757     if (nodeAsTable)
02758     {
02759         StoreURIAttribute(aNode, "background");
02760         return NS_OK;
02761     }
02762 
02763     nsCOMPtr<nsIDOMHTMLTableRowElement> nodeAsTableRow = do_QueryInterface(aNode);
02764     if (nodeAsTableRow)
02765     {
02766         StoreURIAttribute(aNode, "background");
02767         return NS_OK;
02768     }
02769 
02770     nsCOMPtr<nsIDOMHTMLTableCellElement> nodeAsTableCell = do_QueryInterface(aNode);
02771     if (nodeAsTableCell)
02772     {
02773         StoreURIAttribute(aNode, "background");
02774         return NS_OK;
02775     }
02776 
02777     nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNode);
02778     if (nodeAsScript)
02779     {
02780         StoreURIAttribute(aNode, "src");
02781         return NS_OK;
02782     }
02783     
02784     nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNode);
02785     if (nodeAsEmbed)
02786     {
02787         StoreURIAttribute(aNode, "src");
02788         return NS_OK;
02789     }
02790     
02791     nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNode);
02792     if (nodeAsObject)
02793     {
02794         StoreURIAttribute(aNode, "data");
02795         return NS_OK;
02796     }
02797 
02798     nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNode);
02799     if (nodeAsApplet)
02800     {
02801         // For an applet, relative URIs are resolved relative to the
02802         // codebase (which is resolved relative to the base URI).
02803         nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
02804         nsAutoString codebase;
02805         nodeAsApplet->GetCodeBase(codebase);
02806         if (!codebase.IsEmpty()) {
02807             nsCOMPtr<nsIURI> baseURI;
02808             NS_NewURI(getter_AddRefs(baseURI), codebase,
02809                       mCurrentCharset.get(), mCurrentBaseURI);
02810             if (baseURI) {
02811                 mCurrentBaseURI = baseURI;
02812             }
02813         }
02814         StoreURIAttribute(aNode, "code");
02815         StoreURIAttribute(aNode, "archive");
02816         // restore the base URI we really want to have
02817         mCurrentBaseURI = oldBase;
02818         return NS_OK;
02819     }
02820     
02821     nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNode);
02822     if (nodeAsLink)
02823     {
02824         // Test if the link has a rel value indicating it to be a stylesheet
02825         nsAutoString linkRel;
02826         if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty())
02827         {
02828             nsReadingIterator<PRUnichar> start;
02829             nsReadingIterator<PRUnichar> end;
02830             nsReadingIterator<PRUnichar> current;
02831 
02832             linkRel.BeginReading(start);
02833             linkRel.EndReading(end);
02834 
02835             // Walk through space delimited string looking for "stylesheet"
02836             for (current = start; current != end; ++current)
02837             {
02838                 // Ignore whitespace
02839                 if (nsCRT::IsAsciiSpace(*current))
02840                     continue;
02841 
02842                 // Grab the next space delimited word
02843                 nsReadingIterator<PRUnichar> startWord = current;
02844                 do {
02845                     ++current;
02846                 } while (current != end && !nsCRT::IsAsciiSpace(*current));
02847 
02848                 // Store the link for fix up if it says "stylesheet"
02849                 if (Substring(startWord, current)
02850                         .LowerCaseEqualsLiteral("stylesheet"))
02851                 {
02852                     StoreURIAttribute(aNode, "href");
02853                     return NS_OK;
02854                 }
02855                 if (current == end)
02856                     break;
02857             }
02858         }
02859         return NS_OK;
02860     }
02861 
02862     nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNode);
02863     if (nodeAsFrame)
02864     {
02865         URIData *data = nsnull;
02866         StoreURIAttribute(aNode, "src", PR_FALSE, &data);
02867         if (data)
02868         {
02869             data->mIsSubFrame = PR_TRUE;
02870             // Save the frame content
02871             nsCOMPtr<nsIDOMDocument> content;
02872             nodeAsFrame->GetContentDocument(getter_AddRefs(content));
02873             if (content)
02874             {
02875                 nsXPIDLString ext;
02876                 GetDocumentExtension(content, getter_Copies(ext));
02877                 data->mSubFrameExt.AssignLiteral(".");
02878                 data->mSubFrameExt.Append(ext);
02879                 SaveSubframeContent(content, data);
02880             }
02881         }
02882         return NS_OK;
02883     }
02884 
02885     nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNode);
02886     if (nodeAsIFrame && !(mPersistFlags & PERSIST_FLAGS_IGNORE_IFRAMES))
02887     {
02888         URIData *data = nsnull;
02889         StoreURIAttribute(aNode, "src", PR_FALSE, &data);
02890         if (data)
02891         {
02892             data->mIsSubFrame = PR_TRUE;
02893             // Save the frame content
02894             nsCOMPtr<nsIDOMDocument> content;
02895             nodeAsIFrame->GetContentDocument(getter_AddRefs(content));
02896             if (content)
02897             {
02898                 nsXPIDLString ext;
02899                 GetDocumentExtension(content, getter_Copies(ext));
02900                 data->mSubFrameExt.AssignLiteral(".");
02901                 data->mSubFrameExt.Append(ext);
02902                 SaveSubframeContent(content, data);
02903             }
02904         }
02905         return NS_OK;
02906     }
02907 
02908     nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNode);
02909     if (nodeAsInput)
02910     {
02911         StoreURIAttribute(aNode, "src");
02912         return NS_OK;
02913     }
02914 
02915     return NS_OK;
02916 }
02917 
02918 nsresult
02919 nsWebBrowserPersist::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut)
02920 {
02921     if (!(mPersistFlags & PERSIST_FLAGS_FIXUP_ORIGINAL_DOM))
02922     {
02923         nsresult rv = aNodeIn->CloneNode(PR_FALSE, aNodeOut);
02924         NS_ENSURE_SUCCESS(rv, rv);
02925     }
02926     else
02927     {
02928         NS_ADDREF(*aNodeOut = aNodeIn);
02929     }
02930     nsCOMPtr<nsIDOMHTMLElement> element(do_QueryInterface(*aNodeOut));
02931     if (element) {
02932         // Make sure this is not XHTML
02933         nsAutoString namespaceURI;
02934         element->GetNamespaceURI(namespaceURI);
02935         if (namespaceURI.IsEmpty()) {
02936             // This is a tag-soup node.  It may have a _base_href attribute
02937             // stuck on it by the parser, but since we're fixing up all URIs
02938             // relative to the overall document base that will screw us up.
02939             // Just remove the _base_href.
02940             element->RemoveAttribute(NS_LITERAL_STRING("_base_href"));
02941         }
02942     }
02943     return NS_OK;
02944 }
02945 
02946 nsresult
02947 nsWebBrowserPersist::CloneNodeWithFixedUpURIAttributes(
02948     nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut)
02949 {
02950     nsresult rv;
02951     *aNodeOut = nsnull;
02952 
02953     // Fixup xml-stylesheet processing instructions
02954     nsCOMPtr<nsIDOMProcessingInstruction> nodeAsPI = do_QueryInterface(aNodeIn);
02955     if (nodeAsPI)
02956     {
02957         nsAutoString target;
02958         nodeAsPI->GetTarget(target);
02959         if (target.EqualsLiteral("xml-stylesheet"))
02960         {
02961             rv = GetNodeToFixup(aNodeIn, aNodeOut);
02962             if (NS_SUCCEEDED(rv) && *aNodeOut)
02963             {
02964                 nsCOMPtr<nsIDOMProcessingInstruction> outNode = do_QueryInterface(*aNodeOut);
02965                 nsAutoString href;
02966                 GetXMLStyleSheetLink(nodeAsPI, href);
02967                 if (!href.IsEmpty())
02968                 {
02969                     FixupURI(href);
02970                     FixupXMLStyleSheetLink(outNode, href);
02971                 }
02972             }
02973         }
02974     }
02975 
02976     // BASE elements are replaced by a comment so relative links are not hosed.
02977 
02978     if (!(mPersistFlags & PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS))
02979     {
02980         nsCOMPtr<nsIDOMHTMLBaseElement> nodeAsBase = do_QueryInterface(aNodeIn);
02981         if (nodeAsBase)
02982         {
02983             nsCOMPtr<nsIDOMDocument> ownerDocument;
02984             nodeAsBase->GetOwnerDocument(getter_AddRefs(ownerDocument));
02985             if (ownerDocument)
02986             {
02987                 nsAutoString href;
02988                 nodeAsBase->GetHref(href); // Doesn't matter if this fails
02989                 nsCOMPtr<nsIDOMComment> comment;
02990                 nsAutoString commentText; commentText.AssignLiteral(" base ");
02991                 if (!href.IsEmpty())
02992                 {
02993                     commentText += NS_LITERAL_STRING("href=\"") + href + NS_LITERAL_STRING("\" ");
02994                 }
02995                 rv = ownerDocument->CreateComment(commentText, getter_AddRefs(comment));
02996                 if (comment)
02997                 {
02998                     return CallQueryInterface(comment, aNodeOut);
02999                 }
03000             }
03001         }
03002     }
03003 
03004     // Fix up href and file links in the elements
03005 
03006     nsCOMPtr<nsIDOMHTMLAnchorElement> nodeAsAnchor = do_QueryInterface(aNodeIn);
03007     if (nodeAsAnchor)
03008     {
03009         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03010         if (NS_SUCCEEDED(rv) && *aNodeOut)
03011         {
03012             FixupAnchor(*aNodeOut);
03013         }
03014         return rv;
03015     }
03016 
03017     nsCOMPtr<nsIDOMHTMLAreaElement> nodeAsArea = do_QueryInterface(aNodeIn);
03018     if (nodeAsArea)
03019     {
03020         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03021         if (NS_SUCCEEDED(rv) && *aNodeOut)
03022         {
03023             FixupAnchor(*aNodeOut);
03024         }
03025         return rv;
03026     }
03027 
03028     nsCOMPtr<nsIDOMHTMLBodyElement> nodeAsBody = do_QueryInterface(aNodeIn);
03029     if (nodeAsBody)
03030     {
03031         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03032         if (NS_SUCCEEDED(rv) && *aNodeOut)
03033         {
03034             FixupNodeAttribute(*aNodeOut, "background");
03035         }
03036         return rv;
03037     }
03038 
03039     nsCOMPtr<nsIDOMHTMLTableElement> nodeAsTable = do_QueryInterface(aNodeIn);
03040     if (nodeAsTable)
03041     {
03042         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03043         if (NS_SUCCEEDED(rv) && *aNodeOut)
03044         {
03045             FixupNodeAttribute(*aNodeOut, "background");
03046         }
03047         return rv;
03048     }
03049 
03050     nsCOMPtr<nsIDOMHTMLTableRowElement> nodeAsTableRow = do_QueryInterface(aNodeIn);
03051     if (nodeAsTableRow)
03052     {
03053         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03054         if (NS_SUCCEEDED(rv) && *aNodeOut)
03055         {
03056             FixupNodeAttribute(*aNodeOut, "background");
03057         }
03058         return rv;
03059     }
03060 
03061     nsCOMPtr<nsIDOMHTMLTableCellElement> nodeAsTableCell = do_QueryInterface(aNodeIn);
03062     if (nodeAsTableCell)
03063     {
03064         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03065         if (NS_SUCCEEDED(rv) && *aNodeOut)
03066         {
03067             FixupNodeAttribute(*aNodeOut, "background");
03068         }
03069         return rv;
03070     }
03071 
03072     nsCOMPtr<nsIDOMHTMLImageElement> nodeAsImage = do_QueryInterface(aNodeIn);
03073     if (nodeAsImage)
03074     {
03075         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03076         if (NS_SUCCEEDED(rv) && *aNodeOut)
03077         {
03078             // Disable image loads
03079             nsCOMPtr<nsIImageLoadingContent> imgCon =
03080                 do_QueryInterface(*aNodeOut);
03081             if (imgCon)
03082                 imgCon->SetLoadingEnabled(PR_FALSE);
03083 
03084             FixupAnchor(*aNodeOut);
03085             FixupNodeAttribute(*aNodeOut, "src");
03086         }
03087         return rv;
03088     }
03089     
03090     nsCOMPtr<nsIDOMHTMLScriptElement> nodeAsScript = do_QueryInterface(aNodeIn);
03091     if (nodeAsScript)
03092     {
03093         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03094         if (NS_SUCCEEDED(rv) && *aNodeOut)
03095         {
03096             FixupNodeAttribute(*aNodeOut, "src");
03097         }
03098         return rv;
03099     }
03100     
03101     nsCOMPtr<nsIDOMHTMLEmbedElement> nodeAsEmbed = do_QueryInterface(aNodeIn);
03102     if (nodeAsEmbed)
03103     {
03104         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03105         if (NS_SUCCEEDED(rv) && *aNodeOut)
03106         {
03107             FixupNodeAttribute(*aNodeOut, "src");
03108         }
03109         return rv;
03110     }
03111     
03112     nsCOMPtr<nsIDOMHTMLObjectElement> nodeAsObject = do_QueryInterface(aNodeIn);
03113     if (nodeAsObject)
03114     {
03115         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03116         if (NS_SUCCEEDED(rv) && *aNodeOut)
03117         {
03118             FixupNodeAttribute(*aNodeOut, "data");
03119         }
03120         return rv;
03121     }
03122 
03123     nsCOMPtr<nsIDOMHTMLAppletElement> nodeAsApplet = do_QueryInterface(aNodeIn);
03124     if (nodeAsApplet)
03125     {
03126         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03127         if (NS_SUCCEEDED(rv) && *aNodeOut)
03128         {
03129             nsCOMPtr<nsIDOMHTMLAppletElement> newApplet =
03130                 do_QueryInterface(*aNodeOut);
03131             // For an applet, relative URIs are resolved relative to the
03132             // codebase (which is resolved relative to the base URI).
03133             nsCOMPtr<nsIURI> oldBase = mCurrentBaseURI;
03134             nsAutoString codebase;
03135             nodeAsApplet->GetCodeBase(codebase);
03136             if (!codebase.IsEmpty()) {
03137                 nsCOMPtr<nsIURI> baseURI;
03138                 NS_NewURI(getter_AddRefs(baseURI), codebase,
03139                           mCurrentCharset.get(), mCurrentBaseURI);
03140                 if (baseURI) {
03141                     mCurrentBaseURI = baseURI;
03142                 }
03143             }
03144             // Unset the codebase too, since we'll correctly relativize the
03145             // code and archive paths.
03146             newApplet->RemoveAttribute(NS_LITERAL_STRING("codebase"));
03147             FixupNodeAttribute(*aNodeOut, "code");
03148             FixupNodeAttribute(*aNodeOut, "archive");
03149             // restore the base URI we really want to have
03150             mCurrentBaseURI = oldBase;
03151         }
03152         return rv;
03153     }
03154     
03155     nsCOMPtr<nsIDOMHTMLLinkElement> nodeAsLink = do_QueryInterface(aNodeIn);
03156     if (nodeAsLink)
03157     {
03158         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03159         if (NS_SUCCEEDED(rv) && *aNodeOut)
03160         {
03161             // First see if the link represents linked content
03162             rv = FixupNodeAttribute(*aNodeOut, "href");
03163             if (NS_FAILED(rv))
03164             {
03165                 // Perhaps this link is actually an anchor to related content
03166                 FixupAnchor(*aNodeOut);
03167             }
03168             // TODO if "type" attribute == "text/css"
03169             //        fixup stylesheet
03170         }
03171         return rv;
03172     }
03173 
03174     nsCOMPtr<nsIDOMHTMLFrameElement> nodeAsFrame = do_QueryInterface(aNodeIn);
03175     if (nodeAsFrame)
03176     {
03177         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03178         if (NS_SUCCEEDED(rv) && *aNodeOut)
03179         {
03180             FixupNodeAttribute(*aNodeOut, "src");
03181         }
03182         return rv;
03183     }
03184 
03185     nsCOMPtr<nsIDOMHTMLIFrameElement> nodeAsIFrame = do_QueryInterface(aNodeIn);
03186     if (nodeAsIFrame)
03187     {
03188         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03189         if (NS_SUCCEEDED(rv) && *aNodeOut)
03190         {
03191             FixupNodeAttribute(*aNodeOut, "src");
03192         }
03193         return rv;
03194     }
03195 
03196     nsCOMPtr<nsIDOMHTMLInputElement> nodeAsInput = do_QueryInterface(aNodeIn);
03197     if (nodeAsInput)
03198     {
03199         rv = GetNodeToFixup(aNodeIn, aNodeOut);
03200         if (NS_SUCCEEDED(rv) && *aNodeOut)
03201         {
03202             // Disable image loads
03203             nsCOMPtr<nsIImageLoadingContent> imgCon =
03204                 do_QueryInterface(*aNodeOut);
03205             if (imgCon)
03206                 imgCon->SetLoadingEnabled(PR_FALSE);
03207 
03208             FixupNodeAttribute(*aNodeOut, "src");
03209         }
03210         return rv;
03211     }
03212 
03213     return NS_OK;
03214 }
03215 
03216 nsresult
03217 nsWebBrowserPersist::StoreURI(
03218     const char *aURI, PRBool aNeedsPersisting, URIData **aData)
03219 {
03220     NS_ENSURE_ARG_POINTER(aURI);
03221     if (aData)
03222         *aData = nsnull;
03223     
03224     // Test whether this URL should be persisted
03225     PRBool shouldPersistURI = PR_TRUE;
03226     for (PRUint32 i = 0; i < kNonpersistableSchemesSize; i++)
03227     {
03228         PRUint32 schemeLen = strlen(kNonpersistableSchemes[i]);
03229         if (nsCRT::strncasecmp(aURI, kNonpersistableSchemes[i], schemeLen) == 0)
03230         {
03231             shouldPersistURI = PR_FALSE;
03232             break;
03233         }
03234     }
03235     if (shouldPersistURI)
03236     {
03237         URIData *data = nsnull;
03238         MakeAndStoreLocalFilenameInURIMap(aURI, aNeedsPersisting, &data);
03239         if (aData)
03240         {
03241             *aData = data;
03242         }
03243     }
03244 
03245     return NS_OK;
03246 }
03247 
03248 nsresult
03249 nsWebBrowserPersist::StoreURIAttribute(
03250     nsIDOMNode *aNode, const char *aAttribute, PRBool aNeedsPersisting,
03251     URIData **aData)
03252 {
03253     NS_ENSURE_ARG_POINTER(aNode);
03254     NS_ENSURE_ARG_POINTER(aAttribute);
03255 
03256     nsresult rv = NS_OK;
03257 
03258     // Find the named URI attribute on the (element) node and store
03259     // a reference to the URI that maps onto a local file name
03260 
03261     nsCOMPtr<nsIDOMNamedNodeMap> attrMap;
03262     nsCOMPtr<nsIDOMNode> attrNode;
03263     rv = aNode->GetAttributes(getter_AddRefs(attrMap));
03264     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03265 
03266     NS_ConvertASCIItoUTF16 attribute(aAttribute);
03267     rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attrNode));
03268     if (attrNode)
03269     {
03270         nsAutoString oldValue;
03271         attrNode->GetNodeValue(oldValue);
03272         if (!oldValue.IsEmpty())
03273         {
03274             NS_ConvertUCS2toUTF8 oldCValue(oldValue);
03275             return StoreURI(oldCValue.get(), aNeedsPersisting, aData);
03276         }
03277     }
03278 
03279     return NS_OK;
03280 }
03281 
03282 nsresult
03283 nsWebBrowserPersist::FixupURI(nsAString &aURI)
03284 {
03285     // get the current location of the file (absolutized)
03286     nsCOMPtr<nsIURI> uri;
03287     nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, 
03288                             mCurrentCharset.get(), mCurrentBaseURI);
03289     NS_ENSURE_SUCCESS(rv, rv);
03290     nsCAutoString spec;
03291     rv = uri->GetSpec(spec);
03292     NS_ENSURE_SUCCESS(rv, rv);
03293 
03294     // Search for the URI in the map and replace it with the local file
03295     nsCStringKey key(spec.get());
03296     if (!mURIMap.Exists(&key))
03297     {
03298         return NS_ERROR_FAILURE;
03299     }
03300     URIData *data = (URIData *) mURIMap.Get(&key);
03301     if (!data->mNeedsFixup)
03302     {
03303         return NS_OK;
03304     }
03305     nsCOMPtr<nsIURI> fileAsURI;
03306     if (data->mFile)
03307     {
03308         rv = data->mFile->Clone(getter_AddRefs(fileAsURI)); 
03309         NS_ENSURE_SUCCESS(rv, rv);
03310     }
03311     else
03312     {
03313         rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI));
03314         NS_ENSURE_SUCCESS(rv, rv);
03315         rv = AppendPathToURI(fileAsURI, data->mFilename);
03316         NS_ENSURE_SUCCESS(rv, rv);
03317     }
03318     nsAutoString newValue;
03319 
03320     // remove username/password if present
03321     fileAsURI->SetUserPass(EmptyCString());
03322 
03323     // reset node attribute 
03324     // Use relative or absolute links
03325     if (data->mDataPathIsRelative)
03326     {
03327         nsCOMPtr<nsIURL> url(do_QueryInterface(fileAsURI));
03328         if (!url)
03329           return NS_ERROR_FAILURE;
03330           
03331         nsCAutoString filename;
03332         url->GetFileName(filename);
03333 
03334         nsCAutoString rawPathURL(data->mRelativePathToData);
03335         rawPathURL.Append(filename);
03336 
03337         nsCAutoString buf;
03338         AppendUTF8toUTF16(NS_EscapeURL(rawPathURL, esc_FilePath, buf),
03339                           newValue);
03340     }
03341     else
03342     {
03343         nsCAutoString fileurl;
03344         fileAsURI->GetSpec(fileurl);
03345         AppendUTF8toUTF16(fileurl, newValue);
03346     }
03347     if (data->mIsSubFrame)
03348     {
03349         newValue.Append(data->mSubFrameExt);
03350     }
03351 
03352     aURI = newValue;
03353     return NS_OK;
03354 }
03355 
03356 nsresult
03357 nsWebBrowserPersist::FixupNodeAttribute(nsIDOMNode *aNode,
03358                                         const char *aAttribute)
03359 {
03360     NS_ENSURE_ARG_POINTER(aNode);
03361     NS_ENSURE_ARG_POINTER(aAttribute);
03362 
03363     nsresult rv = NS_OK;
03364 
03365     // Find the named URI attribute on the (element) node and change it to reference
03366     // a local file.
03367 
03368     nsCOMPtr<nsIDOMNamedNodeMap> attrMap;
03369     nsCOMPtr<nsIDOMNode> attrNode;
03370     rv = aNode->GetAttributes(getter_AddRefs(attrMap));
03371     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03372 
03373     NS_ConvertASCIItoUTF16 attribute(aAttribute);
03374     rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attrNode));
03375     if (attrNode)
03376     {
03377         nsString uri;
03378         attrNode->GetNodeValue(uri);
03379         rv = FixupURI(uri);
03380         if (NS_SUCCEEDED(rv))
03381         {
03382             attrNode->SetNodeValue(uri);
03383         }
03384     }
03385 
03386     return rv;
03387 }
03388 
03389 nsresult
03390 nsWebBrowserPersist::FixupAnchor(nsIDOMNode *aNode)
03391 {
03392     NS_ENSURE_ARG_POINTER(aNode);
03393 
03394     nsCOMPtr<nsIDOMNamedNodeMap> attrMap;
03395     nsCOMPtr<nsIDOMNode> attrNode;
03396     nsresult rv = aNode->GetAttributes(getter_AddRefs(attrMap));
03397     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03398 
03399     if (mPersistFlags & PERSIST_FLAGS_DONT_FIXUP_LINKS)
03400     {
03401         return NS_OK;
03402     }
03403 
03404     // Make all anchor links absolute so they point off onto the Internet
03405     nsString attribute(NS_LITERAL_STRING("href"));
03406     rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attrNode));
03407     if (attrNode)
03408     {
03409         nsString oldValue;
03410         attrNode->GetNodeValue(oldValue);
03411         NS_ConvertUCS2toUTF8 oldCValue(oldValue);
03412 
03413         // Skip empty values and self-referencing bookmarks
03414         if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#')
03415         {
03416             return NS_OK;
03417         }
03418 
03419         // if saving file to same location, we don't need to do any fixup
03420         PRBool isEqual = PR_FALSE;
03421         if (NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual))
03422             && isEqual)
03423         {
03424             return NS_OK;
03425         }
03426 
03427         nsCOMPtr<nsIURI> relativeURI;
03428         relativeURI = (mPersistFlags & PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION)
03429                       ? mTargetBaseURI : mCurrentBaseURI;
03430         // Make a new URI to replace the current one
03431         nsCOMPtr<nsIURI> newURI;
03432         rv = NS_NewURI(getter_AddRefs(newURI), oldCValue, 
03433                        mCurrentCharset.get(), relativeURI);
03434         if (NS_SUCCEEDED(rv) && newURI)
03435         {
03436             newURI->SetUserPass(EmptyCString());
03437             nsCAutoString uriSpec;
03438             newURI->GetSpec(uriSpec);
03439             attrNode->SetNodeValue(NS_ConvertUTF8toUCS2(uriSpec));
03440         }
03441     }
03442 
03443     return NS_OK;
03444 }
03445 
03446 nsresult
03447 nsWebBrowserPersist::StoreAndFixupStyleSheet(nsIStyleSheet *aStyleSheet)
03448 {
03449     // TODO go through the style sheet fixing up all links
03450     return NS_OK;
03451 }
03452 
03453 nsresult
03454 nsWebBrowserPersist::SaveSubframeContent(
03455     nsIDOMDocument *aFrameContent, URIData *aData)
03456 {
03457     NS_ENSURE_ARG_POINTER(aData);
03458     nsresult rv;
03459 
03460     nsString filenameWithExt = aData->mFilename;
03461     filenameWithExt.Append(aData->mSubFrameExt);
03462 
03463     // Work out the path for the subframe
03464     nsCOMPtr<nsIURI> frameURI;
03465     rv = mCurrentDataPath->Clone(getter_AddRefs(frameURI));
03466     NS_ENSURE_SUCCESS(rv, rv);
03467     rv = AppendPathToURI(frameURI, filenameWithExt);
03468     NS_ENSURE_SUCCESS(rv, rv);
03469 
03470     // Work out the path for the subframe data
03471     nsCOMPtr<nsIURI> frameDataURI;
03472     rv = mCurrentDataPath->Clone(getter_AddRefs(frameDataURI));
03473     NS_ENSURE_SUCCESS(rv, rv);
03474     nsAutoString newFrameDataPath(aData->mFilename);
03475 
03476     // Append _data
03477     newFrameDataPath.AppendLiteral("_data");
03478     rv = AppendPathToURI(frameDataURI, newFrameDataPath);
03479     NS_ENSURE_SUCCESS(rv, rv);
03480 
03481     // Make frame document & data path conformant and unique
03482     rv = CalculateUniqueFilename(frameURI);
03483     NS_ENSURE_SUCCESS(rv, rv);
03484     rv = CalculateUniqueFilename(frameDataURI);
03485     NS_ENSURE_SUCCESS(rv, rv);
03486 
03487     mCurrentThingsToPersist++;
03488     rv = SaveDocumentInternal(aFrameContent, frameURI, frameDataURI);
03489     NS_ENSURE_SUCCESS(rv, rv);
03490 
03491     // Store the updated uri to the frame
03492     aData->mFile = frameURI;
03493     aData->mSubFrameExt.Truncate(); // we already put this in frameURI
03494 
03495     return NS_OK;
03496 }
03497 
03498 nsresult
03499 nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel)
03500 {
03501     nsresult rv = NS_OK;
03502     *aChannel = nsnull;
03503 
03504     nsCOMPtr<nsIIOService> ioserv;
03505     ioserv = do_GetIOService(&rv);
03506     NS_ENSURE_SUCCESS(rv, rv);
03507 
03508     rv = ioserv->NewChannelFromURI(aURI, aChannel);
03509     NS_ENSURE_SUCCESS(rv, rv);
03510     NS_ENSURE_ARG_POINTER(*aChannel);
03511 
03512     rv = (*aChannel)->SetNotificationCallbacks(NS_STATIC_CAST(nsIInterfaceRequestor *, this));
03513     NS_ENSURE_SUCCESS(rv, rv);
03514     return NS_OK;
03515 } 
03516 
03517 nsresult
03518 nsWebBrowserPersist::SaveDocumentWithFixup(
03519     nsIDocument *aDocument, nsIDocumentEncoderNodeFixup *aNodeFixup,
03520     nsIURI *aFile, PRBool aReplaceExisting, const nsACString &aFormatType,
03521     const nsCString &aSaveCharset, PRUint32 aFlags)
03522 {
03523     NS_ENSURE_ARG_POINTER(aFile);
03524     
03525     nsresult  rv = NS_OK;
03526     nsCOMPtr<nsILocalFile> localFile;
03527     GetLocalFileFromURI(aFile, getter_AddRefs(localFile));
03528     if (localFile)
03529     {
03530         // if we're not replacing an existing file but the file
03531         // exists, something is wrong
03532         PRBool fileExists = PR_FALSE;
03533         rv = localFile->Exists(&fileExists);
03534         NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03535 
03536         if (!aReplaceExisting && fileExists)
03537             return NS_ERROR_FAILURE;                // where are the file I/O errors?
03538     }
03539     
03540     nsCOMPtr<nsIOutputStream> outputStream;
03541     rv = MakeOutputStream(aFile, getter_AddRefs(outputStream));
03542     if (NS_FAILED(rv))
03543     {
03544         SendErrorStatusChange(PR_FALSE, rv, nsnull, aFile);
03545         return NS_ERROR_FAILURE;
03546     }
03547     NS_ENSURE_TRUE(outputStream, NS_ERROR_FAILURE);
03548 
03549     // Get a document encoder instance
03550     nsCAutoString contractID(NS_DOC_ENCODER_CONTRACTID_BASE);
03551     contractID.Append(aFormatType);
03552     
03553     nsCOMPtr<nsIDocumentEncoder> encoder = do_CreateInstance(contractID.get(), &rv);
03554     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03555 
03556     NS_ConvertASCIItoUCS2 newContentType(aFormatType);
03557     rv = encoder->Init(aDocument, newContentType, aFlags);
03558     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03559 
03560     mTargetBaseURI = aFile;
03561 
03562     // Set the node fixup callback
03563     encoder->SetNodeFixup(aNodeFixup);
03564 
03565     if (mWrapColumn && (aFlags & ENCODE_FLAGS_WRAP))
03566         encoder->SetWrapColumn(mWrapColumn);
03567 
03568     nsCAutoString charsetStr(aSaveCharset);
03569     if (charsetStr.IsEmpty())
03570         charsetStr = aDocument->GetDocumentCharacterSet();
03571 
03572     rv = encoder->SetCharset(charsetStr);
03573     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03574 
03575     rv = encoder->EncodeToStream(outputStream);
03576     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03577     
03578     if (!localFile)
03579     {
03580         nsCOMPtr<nsIStorageStream> storStream(do_QueryInterface(outputStream));
03581         if (storStream)
03582         {
03583             outputStream->Close();
03584             rv = StartUpload(storStream, aFile, aFormatType);
03585             NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03586         }
03587     }
03588 #if defined(XP_OS2)
03589     else
03590     {
03591         // close the stream, then tag the file it created with its source URI
03592         outputStream->Close();
03593         nsCOMPtr<nsILocalFileOS2> localFileOS2 = do_QueryInterface(localFile);
03594         if (localFileOS2)
03595         {
03596             nsCAutoString url;
03597             mCurrentBaseURI->GetSpec(url);
03598             localFileOS2->SetFileSource(url);
03599         }
03600     }
03601 #endif
03602 
03603     return rv;
03604 }
03605 
03606 
03607 // we store the current location as the key (absolutized version of domnode's attribute's value)
03608 nsresult
03609 nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap(
03610     const char *aURI, PRBool aNeedsPersisting, URIData **aData)
03611 {
03612     NS_ENSURE_ARG_POINTER(aURI);
03613 
03614     nsresult rv;
03615 
03616     // Make a URI
03617     nsCOMPtr<nsIURI> uri;
03618     rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURI), 
03619                    mCurrentCharset.get(), mCurrentBaseURI);
03620     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03621     nsCAutoString spec;
03622     rv = uri->GetSpec(spec);
03623     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03624 
03625     // Create a sensibly named filename for the URI and store in the URI map
03626     nsCStringKey key(spec.get());
03627     if (mURIMap.Exists(&key))
03628     {
03629         if (aData)
03630         {
03631             *aData = (URIData *) mURIMap.Get(&key);
03632         }
03633         return NS_OK;
03634     }
03635 
03636     // Create a unique file name for the uri
03637     nsString filename;
03638     rv = MakeFilenameFromURI(uri, filename);
03639     NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
03640 
03641     // Store the file name
03642     URIData *data = new URIData;
03643     NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY);
03644 
03645     data->mNeedsPersisting = aNeedsPersisting;
03646     data->mNeedsFixup = PR_TRUE;
03647     data->mFilename = filename;
03648     data->mSaved = PR_FALSE;
03649     data->mIsSubFrame = PR_FALSE;
03650     data->mDataPath = mCurrentDataPath;
03651     data->mDataPathIsRelative = mCurrentDataPathIsRelative;
03652     data->mRelativePathToData = mCurrentRelativePathToData;
03653     data->mCharset = mCurrentCharset;
03654 
03655     if (aNeedsPersisting)
03656         mCurrentThingsToPersist++;
03657 
03658     mURIMap.Put(&key, data);
03659     if (aData)
03660     {
03661         *aData = data;
03662     }
03663 
03664     return NS_OK;
03665 }
03666 
03667 struct SpecialXHTMLTags {
03668     PRUnichar name[sizeof(PRUnichar)*11]; // strlen("blockquote")==10
03669 };
03670 
03671 static PRBool IsSpecialXHTMLTag(nsIDOMNode *aNode)
03672 {
03673     nsAutoString ns;
03674     aNode->GetNamespaceURI(ns);
03675     if (!ns.EqualsLiteral("http://www.w3.org/1999/xhtml"))
03676         return PR_FALSE;
03677 
03678     // Ordered so that typical documents work fastest
03679     static SpecialXHTMLTags tags[] = {
03680         {'b','o','d','y',0},
03681         {'h','e','a','d',0},
03682         {'i','m','g',0},
03683         {'s','c','r','i','p','t',0},
03684         {'a',0},
03685         {'a','r','e','a',0},
03686         {'l','i','n','k',0},
03687         {'i','n','p','u','t',0},
03688         {'f','r','a','m','e',0},
03689         {'i','f','r','a','m','e',0},
03690         {'o','b','j','e','c','t',0},
03691         {'a','p','p','l','e','t',0},
03692         {'f','o','r','m',0},
03693         {'b','l','o','c','k','q','u','o','t','e',0},
03694         {'q',0},
03695         {'d','e','l',0},
03696         {'i','n','s',0},
03697         {0}};
03698 
03699     nsAutoString localName;
03700     aNode->GetLocalName(localName);
03701     PRInt32 i;
03702     for (i = 0; tags[i].name[0]; i++) {
03703         if (localName.Equals(tags[i].name))
03704         {
03705             // XXX This element MAY have URI attributes, but
03706             //     we are not actually checking if they are present.
03707             //     That would slow us down further, and I am not so sure
03708             //     how important that would be.
03709             return PR_TRUE;
03710         }
03711     }
03712 
03713     return PR_FALSE;
03714 }
03715 
03716 static PRBool HasSpecialXHTMLTags(nsIDOMNode *aParent)
03717 {
03718     if (IsSpecialXHTMLTag(aParent))
03719         return PR_TRUE;
03720 
03721     nsCOMPtr<nsIDOMNodeList> list;
03722     aParent->GetChildNodes(getter_AddRefs(list));
03723     if (list)
03724     {
03725         PRUint32 count;
03726         list->GetLength(&count);
03727         PRUint32 i;
03728         for (i = 0; i < count; i++) {
03729             nsCOMPtr<nsIDOMNode> node;
03730             list->Item(i, getter_AddRefs(node));
03731             if (!node)
03732                 break;
03733             PRUint16 nodeType;
03734             node->GetNodeType(&nodeType);
03735             if (nodeType == nsIDOMNode::ELEMENT_NODE) {
03736                 return HasSpecialXHTMLTags(node);
03737             }
03738         }
03739     }
03740 
03741     return PR_FALSE;
03742 }
03743 
03744 static PRBool NeedXHTMLBaseTag(nsIDOMDocument *aDocument)
03745 {
03746     nsCOMPtr<nsIDOMElement> docElement;
03747     aDocument->GetDocumentElement(getter_AddRefs(docElement));
03748 
03749     nsCOMPtr<nsIDOMNode> node(do_QueryInterface(docElement));
03750     if (node)
03751     {
03752         return HasSpecialXHTMLTags(node);
03753     }
03754 
03755     return PR_FALSE;
03756 }
03757 
03758 // Set document base. This could create an invalid XML document (still well-formed).
03759 nsresult
03760 nsWebBrowserPersist::SetDocumentBase(
03761     nsIDOMDocument *aDocument, nsIURI *aBaseURI)
03762 {
03763     if (mPersistFlags & PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS)
03764     {
03765         return NS_OK;
03766     }
03767 
03768     NS_ENSURE_ARG_POINTER(aBaseURI);
03769 
03770     nsCOMPtr<nsIDOMXMLDocument> xmlDoc;
03771     nsCOMPtr<nsIDOMHTMLDocument> htmlDoc = do_QueryInterface(aDocument);
03772     if (!htmlDoc)
03773     {
03774         xmlDoc = do_QueryInterface(aDocument);
03775         if (!xmlDoc)
03776         {
03777             return NS_ERROR_FAILURE;
03778         }
03779     }
03780 
03781     NS_NAMED_LITERAL_STRING(kXHTMLNS, "http://www.w3.org/1999/xhtml");
03782     NS_NAMED_LITERAL_STRING(kHead, "head");
03783 
03784     // Find the head element
03785     nsCOMPtr<nsIDOMElement> headElement;
03786     nsCOMPtr<nsIDOMNodeList> headList;
03787     if (xmlDoc)
03788     {
03789         // First see if there is XHTML content that needs base 
03790         // tags.
03791         if (!NeedXHTMLBaseTag(aDocument))
03792             return NS_OK;
03793 
03794         aDocument->GetElementsByTagNameNS(
03795             kXHTMLNS,
03796             kHead, getter_AddRefs(headList));
03797     }
03798     else
03799     {
03800         aDocument->GetElementsByTagName(
03801             kHead, getter_AddRefs(headList));
03802     }
03803     if (headList)
03804     {
03805         nsCOMPtr<nsIDOMNode> headNode;
03806         headList->Item(0, getter_AddRefs(headNode));
03807         headElement = do_QueryInterface(headNode);
03808     }
03809     if (!headElement)
03810     {
03811         // Create head and insert as first element
03812         nsCOMPtr<nsIDOMNode> firstChildNode;
03813         nsCOMPtr<nsIDOMNode> newNode;
03814         if (xmlDoc)
03815         {
03816             aDocument->CreateElementNS(
03817                 kXHTMLNS,
03818                 kHead, getter_AddRefs(headElement));
03819         }
03820         else
03821         {
03822             aDocument->CreateElement(
03823                 kHead, getter_AddRefs(headElement));
03824         }
03825         nsCOMPtr<nsIDOMElement> documentElement;
03826         aDocument->GetDocumentElement(getter_AddRefs(documentElement));
03827         if (documentElement)
03828         {
03829             documentElement->GetFirstChild(getter_AddRefs(firstChildNode));
03830             documentElement->InsertBefore(headElement, firstChildNode, getter_AddRefs(newNode));
03831         }
03832     }
03833     if (!headElement)
03834     {
03835         return NS_ERROR_FAILURE;
03836     }
03837 
03838     // Find or create the BASE element
03839     NS_NAMED_LITERAL_STRING(kBase, "base");
03840     nsCOMPtr<nsIDOMElement> baseElement;
03841     nsCOMPtr<nsIDOMNodeList> baseList;
03842     if (xmlDoc)
03843     {
03844         headElement->GetElementsByTagNameNS(
03845             kXHTMLNS,
03846             kBase, getter_AddRefs(baseList));
03847     }
03848     else
03849     {
03850         headElement->GetElementsByTagName(
03851             kBase, getter_AddRefs(baseList));
03852     }
03853     if (baseList)
03854     {
03855         nsCOMPtr<nsIDOMNode> baseNode;
03856         baseList->Item(0, getter_AddRefs(baseNode));
03857         baseElement = do_QueryInterface(baseNode);
03858     }
03859 
03860     // Add the BASE element
03861     if (!baseElement)
03862     {
03863         if (!baseElement)
03864         {
03865             nsCOMPtr<nsIDOMNode> newNode;
03866             if (xmlDoc)
03867             {
03868                 aDocument->CreateElementNS(
03869                     kXHTMLNS,
03870                     kBase, getter_AddRefs(baseElement));
03871             }
03872             else
03873             {
03874                 aDocument->CreateElement(
03875                     kBase, getter_AddRefs(baseElement));
03876             }
03877             headElement->AppendChild(baseElement, getter_AddRefs(newNode));
03878         }
03879     }
03880     if (!baseElement)
03881     {
03882         return NS_ERROR_FAILURE;
03883     }
03884     nsCAutoString uriSpec;
03885     aBaseURI->GetSpec(uriSpec);
03886     NS_ConvertUTF8toUCS2 href(uriSpec);
03887     baseElement->SetAttribute(NS_LITERAL_STRING("href"), href);
03888 
03889     return NS_OK;
03890 }
03891 
03892 // Decide if we need to apply conversion to the passed channel.
03893 void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel *aChannel)
03894 {
03895     nsresult rv = NS_OK;
03896     nsCOMPtr<nsIEncodedChannel> encChannel = do_QueryInterface(aChannel, &rv);
03897     if (NS_FAILED(rv))
03898         return;
03899 
03900     // Set the default conversion preference:
03901     encChannel->SetApplyConversion(PR_FALSE);
03902 
03903     nsCOMPtr<nsIURI> thisURI;
03904     aChannel->GetURI(getter_AddRefs(thisURI));
03905     nsCOMPtr<nsIURL> sourceURL(do_QueryInterface(thisURI));
03906     if (!sourceURL)
03907         return;
03908     nsCAutoString extension;
03909     sourceURL->GetFileExtension(extension);
03910 
03911     nsCOMPtr<nsIUTF8StringEnumerator> encEnum;
03912     encChannel->GetContentEncodings(getter_AddRefs(encEnum));
03913     if (!encEnum)
03914         return;
03915     nsCOMPtr<nsIExternalHelperAppService> helperAppService =
03916         do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
03917     if (NS_FAILED(rv))
03918         return;
03919     PRBool hasMore;
03920     rv = encEnum->HasMore(&hasMore);
03921     if (NS_SUCCEEDED(rv) && hasMore)
03922     {
03923         nsCAutoString encType;
03924         rv = encEnum->GetNext(encType);
03925         if (NS_SUCCEEDED(rv))
03926         {
03927             PRBool applyConversion = PR_FALSE;
03928             rv = helperAppService->ApplyDecodingForExtension(extension, encType,
03929                                                              &applyConversion);
03930             if (NS_SUCCEEDED(rv))
03931                 encChannel->SetApplyConversion(applyConversion);
03932         }
03933     }
03934 }
03935 
03937 
03938 
03939 nsEncoderNodeFixup::nsEncoderNodeFixup() : mWebBrowserPersist(nsnull)
03940 {
03941 }
03942 
03943 
03944 nsEncoderNodeFixup::~nsEncoderNodeFixup()
03945 {
03946 }
03947 
03948 
03949 NS_IMPL_ADDREF(nsEncoderNodeFixup)
03950 NS_IMPL_RELEASE(nsEncoderNodeFixup)
03951 
03952 
03953 NS_INTERFACE_MAP_BEGIN(nsEncoderNodeFixup)
03954     NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentEncoderNodeFixup)
03955     NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoderNodeFixup)
03956 NS_INTERFACE_MAP_END
03957 
03958 
03959 NS_IMETHODIMP nsEncoderNodeFixup::FixupNode(
03960     nsIDOMNode *aNode, nsIDOMNode **aOutNode)
03961 {
03962     NS_ENSURE_ARG_POINTER(aNode);
03963     NS_ENSURE_ARG_POINTER(aOutNode);
03964     NS_ENSURE_TRUE(mWebBrowserPersist, NS_ERROR_FAILURE);
03965 
03966     *aOutNode = nsnull;
03967     
03968     // Test whether we need to fixup the node
03969     PRUint16 type = 0;
03970     aNode->GetNodeType(&type);
03971     if (type == nsIDOMNode::ELEMENT_NODE ||
03972         type == nsIDOMNode::PROCESSING_INSTRUCTION_NODE)
03973     {
03974         return mWebBrowserPersist->CloneNodeWithFixedUpURIAttributes(aNode, aOutNode);
03975     }
03976 
03977     return NS_OK;
03978 }