michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "mozilla/ArrayUtils.h" michael@0: michael@0: #include "nspr.h" michael@0: michael@0: #include "nsIFileStreams.h" // New Necko file streams michael@0: #include michael@0: michael@0: #include "nsNetUtil.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsIComponentRegistrar.h" michael@0: #include "nsIStorageStream.h" michael@0: #include "nsISeekableStream.h" michael@0: #include "nsIHttpChannel.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsIEncodedChannel.h" michael@0: #include "nsIUploadChannel.h" michael@0: #include "nsICachingChannel.h" michael@0: #include "nsIFileChannel.h" michael@0: #include "nsEscape.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsIStringEnumerator.h" michael@0: #include "nsCRT.h" michael@0: #include "nsSupportsArray.h" michael@0: #include "nsContentCID.h" michael@0: #include "nsStreamUtils.h" michael@0: michael@0: #include "nsCExternalHandlerService.h" michael@0: michael@0: #include "nsIURL.h" michael@0: #include "nsIFileURL.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMXMLDocument.h" michael@0: #include "nsIDOMTreeWalker.h" michael@0: #include "nsIDOMNode.h" michael@0: #include "nsIDOMComment.h" michael@0: #include "nsIDOMMozNamedAttrMap.h" michael@0: #include "nsIDOMAttr.h" michael@0: #include "nsIDOMNodeList.h" michael@0: #include "nsIWebProgressListener.h" michael@0: #include "nsIAuthPrompt.h" michael@0: #include "nsIPrompt.h" michael@0: #include "nsISHEntry.h" michael@0: #include "nsIWebPageDescriptor.h" michael@0: #include "nsIFormControl.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: #include "nsIDOMNodeFilter.h" michael@0: #include "nsIDOMProcessingInstruction.h" michael@0: #include "nsIDOMHTMLAnchorElement.h" michael@0: #include "nsIDOMHTMLAreaElement.h" michael@0: #include "nsIDOMHTMLCollection.h" michael@0: #include "nsIDOMHTMLImageElement.h" michael@0: #include "nsIDOMHTMLScriptElement.h" michael@0: #include "nsIDOMHTMLLinkElement.h" michael@0: #include "nsIDOMHTMLBaseElement.h" michael@0: #include "nsIDOMHTMLFrameElement.h" michael@0: #include "nsIDOMHTMLIFrameElement.h" michael@0: #include "nsIDOMHTMLInputElement.h" michael@0: #include "nsIDOMHTMLEmbedElement.h" michael@0: #include "nsIDOMHTMLObjectElement.h" michael@0: #include "nsIDOMHTMLAppletElement.h" michael@0: #include "nsIDOMHTMLOptionElement.h" michael@0: #include "nsIDOMHTMLTextAreaElement.h" michael@0: #include "nsIDOMHTMLDocument.h" michael@0: #include "nsIDOMHTMLSourceElement.h" michael@0: #include "nsIDOMHTMLMediaElement.h" michael@0: michael@0: #include "nsIImageLoadingContent.h" michael@0: michael@0: #include "ftpCore.h" michael@0: #include "nsITransport.h" michael@0: #include "nsISocketTransport.h" michael@0: #include "nsIStringBundle.h" michael@0: #include "nsIProtocolHandler.h" michael@0: michael@0: #include "nsWebBrowserPersist.h" michael@0: michael@0: #include "nsIContent.h" michael@0: #include "nsIMIMEInfo.h" michael@0: #include "mozilla/dom/HTMLInputElement.h" michael@0: #include "mozilla/dom/HTMLSharedElement.h" michael@0: #include "mozilla/dom/HTMLSharedObjectElement.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::dom; michael@0: michael@0: // Buffer file writes in 32kb chunks michael@0: #define BUFFERED_OUTPUT_SIZE (1024 * 32) michael@0: michael@0: // Information about a DOM document michael@0: struct DocData michael@0: { michael@0: nsCOMPtr mBaseURI; michael@0: nsCOMPtr mDocument; michael@0: nsCOMPtr mFile; michael@0: nsCOMPtr mDataPath; michael@0: bool mDataPathIsRelative; michael@0: nsCString mRelativePathToData; michael@0: nsCString mCharset; michael@0: }; michael@0: michael@0: // Information about a URI michael@0: struct URIData michael@0: { michael@0: bool mNeedsPersisting; michael@0: bool mSaved; michael@0: bool mIsSubFrame; michael@0: bool mDataPathIsRelative; michael@0: bool mNeedsFixup; michael@0: nsString mFilename; michael@0: nsString mSubFrameExt; michael@0: nsCOMPtr mFile; michael@0: nsCOMPtr mDataPath; michael@0: nsCString mRelativePathToData; michael@0: nsCString mCharset; michael@0: }; michael@0: michael@0: // Information about the output stream michael@0: struct OutputData michael@0: { michael@0: nsCOMPtr mFile; michael@0: nsCOMPtr mOriginalLocation; michael@0: nsCOMPtr mStream; michael@0: int64_t mSelfProgress; michael@0: int64_t mSelfProgressMax; michael@0: bool mCalcFileExt; michael@0: michael@0: OutputData(nsIURI *aFile, nsIURI *aOriginalLocation, bool aCalcFileExt) : michael@0: mFile(aFile), michael@0: mOriginalLocation(aOriginalLocation), michael@0: mSelfProgress(0), michael@0: mSelfProgressMax(10000), michael@0: mCalcFileExt(aCalcFileExt) michael@0: { michael@0: } michael@0: ~OutputData() michael@0: { michael@0: if (mStream) michael@0: { michael@0: mStream->Close(); michael@0: } michael@0: } michael@0: }; michael@0: michael@0: struct UploadData michael@0: { michael@0: nsCOMPtr mFile; michael@0: int64_t mSelfProgress; michael@0: int64_t mSelfProgressMax; michael@0: michael@0: UploadData(nsIURI *aFile) : michael@0: mFile(aFile), michael@0: mSelfProgress(0), michael@0: mSelfProgressMax(10000) michael@0: { michael@0: } michael@0: }; michael@0: michael@0: struct CleanupData michael@0: { michael@0: nsCOMPtr mFile; michael@0: // Snapshot of what the file actually is at the time of creation so that if michael@0: // it transmutes into something else later on it can be ignored. For example, michael@0: // catch files that turn into dirs or vice versa. michael@0: bool mIsDirectory; michael@0: }; michael@0: michael@0: // Maximum file length constant. The max file name length is michael@0: // volume / server dependent but it is difficult to obtain michael@0: // that information. Instead this constant is a reasonable value that michael@0: // modern systems should able to cope with. michael@0: const uint32_t kDefaultMaxFilenameLength = 64; michael@0: michael@0: // Default flags for persistence michael@0: const uint32_t kDefaultPersistFlags = michael@0: nsIWebBrowserPersist::PERSIST_FLAGS_NO_CONVERSION | michael@0: nsIWebBrowserPersist::PERSIST_FLAGS_REPLACE_EXISTING_FILES; michael@0: michael@0: // String bundle where error messages come from michael@0: const char *kWebBrowserPersistStringBundle = michael@0: "chrome://global/locale/nsWebBrowserPersist.properties"; michael@0: michael@0: nsWebBrowserPersist::nsWebBrowserPersist() : michael@0: mCurrentThingsToPersist(0), michael@0: mFirstAndOnlyUse(true), michael@0: mCancel(false), michael@0: mJustStartedLoading(true), michael@0: mCompleted(false), michael@0: mStartSaving(false), michael@0: mReplaceExisting(true), michael@0: mSerializingOutput(false), michael@0: mIsPrivate(false), michael@0: mPersistFlags(kDefaultPersistFlags), michael@0: mPersistResult(NS_OK), michael@0: mTotalCurrentProgress(0), michael@0: mTotalMaxProgress(0), michael@0: mWrapColumn(72), michael@0: mEncodingFlags(0) michael@0: { michael@0: } michael@0: michael@0: nsWebBrowserPersist::~nsWebBrowserPersist() michael@0: { michael@0: Cleanup(); michael@0: } michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist::nsISupports michael@0: //***************************************************************************** michael@0: michael@0: NS_IMPL_ADDREF(nsWebBrowserPersist) michael@0: NS_IMPL_RELEASE(nsWebBrowserPersist) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsWebBrowserPersist) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIWebBrowserPersist) michael@0: NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersist) michael@0: NS_INTERFACE_MAP_ENTRY(nsICancelable) michael@0: NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) michael@0: NS_INTERFACE_MAP_ENTRY(nsIStreamListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) michael@0: NS_INTERFACE_MAP_ENTRY(nsIProgressEventSink) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist::nsIInterfaceRequestor michael@0: //***************************************************************************** michael@0: michael@0: NS_IMETHODIMP nsWebBrowserPersist::GetInterface(const nsIID & aIID, void **aIFace) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aIFace); michael@0: michael@0: *aIFace = nullptr; michael@0: michael@0: nsresult rv = QueryInterface(aIID, aIFace); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: return rv; michael@0: } michael@0: michael@0: if (mProgressListener && (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) michael@0: || aIID.Equals(NS_GET_IID(nsIPrompt)))) michael@0: { michael@0: mProgressListener->QueryInterface(aIID, aIFace); michael@0: if (*aIFace) michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr req = do_QueryInterface(mProgressListener); michael@0: if (req) michael@0: { michael@0: return req->GetInterface(aIID, aIFace); michael@0: } michael@0: michael@0: return NS_ERROR_NO_INTERFACE; michael@0: } michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist::nsIWebBrowserPersist michael@0: //***************************************************************************** michael@0: michael@0: /* attribute unsigned long persistFlags; */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::GetPersistFlags(uint32_t *aPersistFlags) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aPersistFlags); michael@0: *aPersistFlags = mPersistFlags; michael@0: return NS_OK; michael@0: } michael@0: NS_IMETHODIMP nsWebBrowserPersist::SetPersistFlags(uint32_t aPersistFlags) michael@0: { michael@0: mPersistFlags = aPersistFlags; michael@0: mReplaceExisting = (mPersistFlags & PERSIST_FLAGS_REPLACE_EXISTING_FILES) ? true : false; michael@0: mSerializingOutput = (mPersistFlags & PERSIST_FLAGS_SERIALIZE_OUTPUT) ? true : false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute unsigned long currentState; */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::GetCurrentState(uint32_t *aCurrentState) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aCurrentState); michael@0: if (mCompleted) michael@0: { michael@0: *aCurrentState = PERSIST_STATE_FINISHED; michael@0: } michael@0: else if (mFirstAndOnlyUse) michael@0: { michael@0: *aCurrentState = PERSIST_STATE_SAVING; michael@0: } michael@0: else michael@0: { michael@0: *aCurrentState = PERSIST_STATE_READY; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* readonly attribute unsigned long result; */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::GetResult(nsresult *aResult) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aResult); michael@0: *aResult = mPersistResult; michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* attribute nsIWebBrowserPersistProgress progressListener; */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::GetProgressListener( michael@0: nsIWebProgressListener * *aProgressListener) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aProgressListener); michael@0: *aProgressListener = mProgressListener; michael@0: NS_IF_ADDREF(*aProgressListener); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsWebBrowserPersist::SetProgressListener( michael@0: nsIWebProgressListener * aProgressListener) michael@0: { michael@0: mProgressListener = aProgressListener; michael@0: mProgressListener2 = do_QueryInterface(aProgressListener); michael@0: mEventSink = do_GetInterface(aProgressListener); michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void saveURI (in nsIURI aURI, in nsISupports aCacheKey, in nsIURI aReferrer, michael@0: in nsIInputStream aPostData, in wstring aExtraHeaders, michael@0: in nsISupports aFile, in nsILoadContext aPrivayContext); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::SaveURI( michael@0: nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, nsIInputStream *aPostData, const char *aExtraHeaders, nsISupports *aFile, nsILoadContext* aPrivacyContext) michael@0: { michael@0: return SavePrivacyAwareURI(aURI, aCacheKey, aReferrer, aPostData, aExtraHeaders, aFile, michael@0: aPrivacyContext && aPrivacyContext->UsePrivateBrowsing()); michael@0: } michael@0: michael@0: NS_IMETHODIMP nsWebBrowserPersist::SavePrivacyAwareURI( michael@0: nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, nsIInputStream *aPostData, const char *aExtraHeaders, nsISupports *aFile, bool aIsPrivate) michael@0: { michael@0: NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); michael@0: mFirstAndOnlyUse = false; // Stop people from reusing this object! michael@0: michael@0: nsCOMPtr fileAsURI; michael@0: nsresult rv; michael@0: rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); michael@0: michael@0: // SaveURI doesn't like broken uris. michael@0: mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; michael@0: rv = SaveURIInternal(aURI, aCacheKey, aReferrer, aPostData, aExtraHeaders, fileAsURI, false, aIsPrivate); michael@0: return NS_FAILED(rv) ? rv : NS_OK; michael@0: } michael@0: michael@0: /* void saveChannel (in nsIChannel aChannel, in nsISupports aFile); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::SaveChannel( michael@0: nsIChannel *aChannel, nsISupports *aFile) michael@0: { michael@0: NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); michael@0: mFirstAndOnlyUse = false; // Stop people from reusing this object! michael@0: michael@0: nsCOMPtr fileAsURI; michael@0: nsresult rv; michael@0: rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); michael@0: michael@0: rv = aChannel->GetURI(getter_AddRefs(mURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // SaveURI doesn't like broken uris. michael@0: mPersistFlags |= PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS; michael@0: rv = SaveChannelInternal(aChannel, fileAsURI, false); michael@0: return NS_FAILED(rv) ? rv : NS_OK; michael@0: } michael@0: michael@0: michael@0: /* void saveDocument (in nsIDOMDocument aDocument, in nsIURI aFileURI, michael@0: in nsIURI aDataPathURI, in string aOutputContentType, michael@0: in unsigned long aEncodingFlags, in unsigned long aWrapColumn); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::SaveDocument( michael@0: nsIDOMDocument *aDocument, nsISupports *aFile, nsISupports *aDataPath, michael@0: const char *aOutputContentType, uint32_t aEncodingFlags, uint32_t aWrapColumn) michael@0: { michael@0: NS_ENSURE_TRUE(mFirstAndOnlyUse, NS_ERROR_FAILURE); michael@0: mFirstAndOnlyUse = false; // Stop people from reusing this object! michael@0: michael@0: nsCOMPtr fileAsURI; michael@0: nsCOMPtr datapathAsURI; michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr doc = do_QueryInterface(aDocument); michael@0: nsCOMPtr privacyContext = doc ? doc->GetLoadContext() : nullptr; michael@0: mIsPrivate = privacyContext && privacyContext->UsePrivateBrowsing(); michael@0: michael@0: rv = GetValidURIFromObject(aFile, getter_AddRefs(fileAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); michael@0: if (aDataPath) michael@0: { michael@0: rv = GetValidURIFromObject(aDataPath, getter_AddRefs(datapathAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_INVALID_ARG); michael@0: } michael@0: michael@0: mWrapColumn = aWrapColumn; michael@0: michael@0: // Produce nsIDocumentEncoder encoding flags michael@0: mEncodingFlags = 0; michael@0: if (aEncodingFlags & ENCODE_FLAGS_SELECTION_ONLY) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputSelectionOnly; michael@0: if (aEncodingFlags & ENCODE_FLAGS_FORMATTED) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputFormatted; michael@0: if (aEncodingFlags & ENCODE_FLAGS_RAW) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputRaw; michael@0: if (aEncodingFlags & ENCODE_FLAGS_BODY_ONLY) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputBodyOnly; michael@0: if (aEncodingFlags & ENCODE_FLAGS_PREFORMATTED) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputPreformatted; michael@0: if (aEncodingFlags & ENCODE_FLAGS_WRAP) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputWrap; michael@0: if (aEncodingFlags & ENCODE_FLAGS_FORMAT_FLOWED) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputFormatFlowed; michael@0: if (aEncodingFlags & ENCODE_FLAGS_ABSOLUTE_LINKS) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputAbsoluteLinks; michael@0: if (aEncodingFlags & ENCODE_FLAGS_ENCODE_BASIC_ENTITIES) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputEncodeBasicEntities; michael@0: if (aEncodingFlags & ENCODE_FLAGS_ENCODE_LATIN1_ENTITIES) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputEncodeLatin1Entities; michael@0: if (aEncodingFlags & ENCODE_FLAGS_ENCODE_HTML_ENTITIES) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputEncodeHTMLEntities; michael@0: if (aEncodingFlags & ENCODE_FLAGS_ENCODE_W3C_ENTITIES) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputEncodeW3CEntities; michael@0: if (aEncodingFlags & ENCODE_FLAGS_CR_LINEBREAKS) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputCRLineBreak; michael@0: if (aEncodingFlags & ENCODE_FLAGS_LF_LINEBREAKS) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputLFLineBreak; michael@0: if (aEncodingFlags & ENCODE_FLAGS_NOSCRIPT_CONTENT) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputNoScriptContent; michael@0: if (aEncodingFlags & ENCODE_FLAGS_NOFRAMES_CONTENT) michael@0: mEncodingFlags |= nsIDocumentEncoder::OutputNoFramesContent; michael@0: michael@0: if (aOutputContentType) michael@0: { michael@0: mContentType.AssignASCII(aOutputContentType); michael@0: } michael@0: michael@0: rv = SaveDocumentInternal(aDocument, fileAsURI, datapathAsURI); michael@0: michael@0: // Now save the URIs that have been gathered michael@0: michael@0: if (NS_SUCCEEDED(rv) && datapathAsURI) michael@0: { michael@0: rv = SaveGatheredURIs(fileAsURI); michael@0: } michael@0: else if (mProgressListener) michael@0: { michael@0: // tell the listener we're done michael@0: mProgressListener->OnStateChange(nullptr, nullptr, michael@0: nsIWebProgressListener::STATE_START | michael@0: nsIWebProgressListener::STATE_IS_NETWORK, michael@0: NS_OK); michael@0: mProgressListener->OnStateChange(nullptr, nullptr, michael@0: nsIWebProgressListener::STATE_STOP | michael@0: nsIWebProgressListener::STATE_IS_NETWORK, michael@0: rv); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /* void cancel(nsresult aReason); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::Cancel(nsresult aReason) michael@0: { michael@0: mCancel = true; michael@0: EndDownload(aReason); michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: /* void cancelSave(); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::CancelSave() michael@0: { michael@0: return Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::StartUpload(nsIStorageStream *storStream, michael@0: nsIURI *aDestinationURI, const nsACString &aContentType) michael@0: { michael@0: // setup the upload channel if the destination is not local michael@0: nsCOMPtr inputstream; michael@0: nsresult rv = storStream->NewInputStream(0, getter_AddRefs(inputstream)); michael@0: NS_ENSURE_TRUE(inputstream, NS_ERROR_FAILURE); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: return StartUpload(inputstream, aDestinationURI, aContentType); michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::StartUpload(nsIInputStream *aInputStream, michael@0: nsIURI *aDestinationURI, const nsACString &aContentType) michael@0: { michael@0: nsCOMPtr destChannel; michael@0: CreateChannelFromURI(aDestinationURI, getter_AddRefs(destChannel)); michael@0: nsCOMPtr uploadChannel(do_QueryInterface(destChannel)); michael@0: NS_ENSURE_TRUE(uploadChannel, NS_ERROR_FAILURE); michael@0: michael@0: // Set the upload stream michael@0: // NOTE: ALL data must be available in "inputstream" michael@0: nsresult rv = uploadChannel->SetUploadStream(aInputStream, aContentType, -1); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: rv = destChannel->AsyncOpen(this, nullptr); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // add this to the upload list michael@0: nsCOMPtr keyPtr = do_QueryInterface(destChannel); michael@0: mUploadList.Put(keyPtr, new UploadData(aDestinationURI)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::SaveGatheredURIs(nsIURI *aFileAsURI) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: // Count how many URIs in the URI map require persisting michael@0: uint32_t urisToPersist = 0; michael@0: if (mURIMap.Count() > 0) michael@0: { michael@0: mURIMap.EnumerateRead(EnumCountURIsToPersist, &urisToPersist); michael@0: } michael@0: michael@0: if (urisToPersist > 0) michael@0: { michael@0: // Persist each file in the uri map. The document(s) michael@0: // will be saved after the last one of these is saved. michael@0: mURIMap.EnumerateRead(EnumPersistURIs, this); michael@0: } michael@0: michael@0: // if we don't have anything in mOutputMap (added from above enumeration) michael@0: // then we build the doc list (SaveDocuments) michael@0: if (mOutputMap.Count() == 0) michael@0: { michael@0: // There are no URIs to save, so just save the document(s) michael@0: michael@0: // State start notification michael@0: uint32_t addToStateFlags = 0; michael@0: if (mProgressListener) michael@0: { michael@0: if (mJustStartedLoading) michael@0: { michael@0: addToStateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; michael@0: } michael@0: mProgressListener->OnStateChange(nullptr, nullptr, michael@0: nsIWebProgressListener::STATE_START | addToStateFlags, NS_OK); michael@0: } michael@0: michael@0: rv = SaveDocuments(); michael@0: if (NS_FAILED(rv)) michael@0: EndDownload(rv); michael@0: else if (aFileAsURI) michael@0: { michael@0: // local files won't trigger OnStopRequest so we call EndDownload here michael@0: bool isFile = false; michael@0: aFileAsURI->SchemeIs("file", &isFile); michael@0: if (isFile) michael@0: EndDownload(NS_OK); michael@0: } michael@0: michael@0: // State stop notification michael@0: if (mProgressListener) michael@0: { michael@0: mProgressListener->OnStateChange(nullptr, nullptr, michael@0: nsIWebProgressListener::STATE_STOP | addToStateFlags, rv); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: // this method returns true if there is another file to persist and false if not michael@0: bool michael@0: nsWebBrowserPersist::SerializeNextFile() michael@0: { michael@0: if (!mSerializingOutput) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: nsresult rv = SaveGatheredURIs(nullptr); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: return false; michael@0: } michael@0: michael@0: return (mURIMap.Count() michael@0: || mUploadList.Count() michael@0: || mDocList.Length() michael@0: || mOutputMap.Count()); michael@0: } michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist::nsIRequestObserver michael@0: //***************************************************************************** michael@0: michael@0: NS_IMETHODIMP nsWebBrowserPersist::OnStartRequest( michael@0: nsIRequest* request, nsISupports *ctxt) michael@0: { michael@0: if (mProgressListener) michael@0: { michael@0: uint32_t stateFlags = nsIWebProgressListener::STATE_START | michael@0: nsIWebProgressListener::STATE_IS_REQUEST; michael@0: if (mJustStartedLoading) michael@0: { michael@0: stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; michael@0: } michael@0: mProgressListener->OnStateChange(nullptr, request, stateFlags, NS_OK); michael@0: } michael@0: michael@0: mJustStartedLoading = false; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr keyPtr = do_QueryInterface(request); michael@0: OutputData *data = mOutputMap.Get(keyPtr); michael@0: michael@0: // NOTE: This code uses the channel as a hash key so it will not michael@0: // recognize redirected channels because the key is not the same. michael@0: // When that happens we remove and add the data entry to use the michael@0: // new channel as the hash key. michael@0: if (!data) michael@0: { michael@0: UploadData *upData = mUploadList.Get(keyPtr); michael@0: if (!upData) michael@0: { michael@0: // Redirect? Try and fixup the output table michael@0: nsresult rv = FixRedirectedChannelEntry(channel); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // Should be able to find the data after fixup unless redirects michael@0: // are disabled. michael@0: data = mOutputMap.Get(keyPtr); michael@0: if (!data) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (data && data->mFile) michael@0: { michael@0: // If PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION is set in mPersistFlags, michael@0: // try to determine whether this channel needs to apply Content-Encoding michael@0: // conversions. michael@0: NS_ASSERTION(!((mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) && michael@0: (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION)), michael@0: "Conflict in persist flags: both AUTODETECT and NO_CONVERSION set"); michael@0: if (mPersistFlags & PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION) michael@0: SetApplyConversionIfNeeded(channel); michael@0: michael@0: if (data->mCalcFileExt && !(mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES)) michael@0: { michael@0: // this is the first point at which the server can tell us the mimetype michael@0: CalculateAndAppendFileExt(data->mFile, channel, data->mOriginalLocation); michael@0: michael@0: // now make filename conformant and unique michael@0: CalculateUniqueFilename(data->mFile); michael@0: } michael@0: michael@0: // compare uris and bail before we add to output map if they are equal michael@0: bool isEqual = false; michael@0: if (NS_SUCCEEDED(data->mFile->Equals(data->mOriginalLocation, &isEqual)) michael@0: && isEqual) michael@0: { michael@0: // remove from output map michael@0: mOutputMap.Remove(keyPtr); michael@0: michael@0: // cancel; we don't need to know any more michael@0: // stop request will get called michael@0: request->Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP nsWebBrowserPersist::OnStopRequest( michael@0: nsIRequest* request, nsISupports *ctxt, nsresult status) michael@0: { michael@0: nsCOMPtr keyPtr = do_QueryInterface(request); michael@0: OutputData *data = mOutputMap.Get(keyPtr); michael@0: if (data) michael@0: { michael@0: if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(status)) michael@0: SendErrorStatusChange(true, status, request, data->mFile); michael@0: michael@0: // This will automatically close the output stream michael@0: mOutputMap.Remove(keyPtr); michael@0: } michael@0: else michael@0: { michael@0: // if we didn't find the data in mOutputMap, try mUploadList michael@0: UploadData *upData = mUploadList.Get(keyPtr); michael@0: if (upData) michael@0: { michael@0: mUploadList.Remove(keyPtr); michael@0: } michael@0: } michael@0: michael@0: // ensure we call SaveDocuments if we: michael@0: // 1) aren't canceling michael@0: // 2) we haven't triggered the save (which we only want to trigger once) michael@0: // 3) we aren't serializing (which will call it inside SerializeNextFile) michael@0: if (mOutputMap.Count() == 0 && !mCancel && !mStartSaving && !mSerializingOutput) michael@0: { michael@0: nsresult rv = SaveDocuments(); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: bool completed = false; michael@0: if (mOutputMap.Count() == 0 && mUploadList.Count() == 0 && !mCancel) michael@0: { michael@0: // if no documents left in mDocList, --> done michael@0: // if we have no files left to serialize and no error result, --> done michael@0: if (mDocList.Length() == 0 michael@0: || (!SerializeNextFile() && NS_SUCCEEDED(mPersistResult))) michael@0: { michael@0: completed = true; michael@0: } michael@0: } michael@0: michael@0: if (completed) michael@0: { michael@0: // we're all done, do our cleanup michael@0: EndDownload(status); michael@0: } michael@0: michael@0: if (mProgressListener) michael@0: { michael@0: uint32_t stateFlags = nsIWebProgressListener::STATE_STOP | michael@0: nsIWebProgressListener::STATE_IS_REQUEST; michael@0: if (completed) michael@0: { michael@0: stateFlags |= nsIWebProgressListener::STATE_IS_NETWORK; michael@0: } michael@0: mProgressListener->OnStateChange(nullptr, request, stateFlags, status); michael@0: } michael@0: if (completed) michael@0: { michael@0: mProgressListener = nullptr; michael@0: mProgressListener2 = nullptr; michael@0: mEventSink = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist::nsIStreamListener michael@0: //***************************************************************************** michael@0: michael@0: NS_IMETHODIMP michael@0: nsWebBrowserPersist::OnDataAvailable( michael@0: nsIRequest* request, nsISupports *aContext, nsIInputStream *aIStream, michael@0: uint64_t aOffset, uint32_t aLength) michael@0: { michael@0: bool cancel = mCancel; michael@0: if (!cancel) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: uint32_t bytesRemaining = aLength; michael@0: michael@0: nsCOMPtr channel = do_QueryInterface(request); michael@0: NS_ENSURE_TRUE(channel, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr keyPtr = do_QueryInterface(request); michael@0: OutputData *data = mOutputMap.Get(keyPtr); michael@0: if (!data) { michael@0: // might be uploadData; consume necko's buffer and bail... michael@0: uint32_t n; michael@0: return aIStream->ReadSegments(NS_DiscardSegment, nullptr, aLength, &n); michael@0: } michael@0: michael@0: bool readError = true; michael@0: michael@0: // Make the output stream michael@0: if (!data->mStream) michael@0: { michael@0: rv = MakeOutputStream(data->mFile, getter_AddRefs(data->mStream)); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: readError = false; michael@0: cancel = true; michael@0: } michael@0: } michael@0: michael@0: // Read data from the input and write to the output michael@0: char buffer[8192]; michael@0: uint32_t bytesRead; michael@0: while (!cancel && bytesRemaining) michael@0: { michael@0: readError = true; michael@0: rv = aIStream->Read(buffer, michael@0: std::min(uint32_t(sizeof(buffer)), bytesRemaining), michael@0: &bytesRead); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: readError = false; michael@0: // Write out the data until something goes wrong, or, it is michael@0: // all written. We loop because for some errors (e.g., disk michael@0: // full), we get NS_OK with some bytes written, then an error. michael@0: // So, we want to write again in that case to get the actual michael@0: // error code. michael@0: const char *bufPtr = buffer; // Where to write from. michael@0: while (NS_SUCCEEDED(rv) && bytesRead) michael@0: { michael@0: uint32_t bytesWritten = 0; michael@0: rv = data->mStream->Write(bufPtr, bytesRead, &bytesWritten); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: bytesRead -= bytesWritten; michael@0: bufPtr += bytesWritten; michael@0: bytesRemaining -= bytesWritten; michael@0: // Force an error if (for some reason) we get NS_OK but michael@0: // no bytes written. michael@0: if (!bytesWritten) michael@0: { michael@0: rv = NS_ERROR_FAILURE; michael@0: cancel = true; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // Disaster - can't write out the bytes - disk full / permission? michael@0: cancel = true; michael@0: } michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // Disaster - can't read the bytes - broken link / file error? michael@0: cancel = true; michael@0: } michael@0: } michael@0: michael@0: int64_t channelContentLength = -1; michael@0: if (!cancel && michael@0: NS_SUCCEEDED(channel->GetContentLength(&channelContentLength))) michael@0: { michael@0: // if we get -1 at this point, we didn't get content-length header michael@0: // assume that we got all of the data and push what we have; michael@0: // that's the best we can do now michael@0: if ((-1 == channelContentLength) || michael@0: ((channelContentLength - (aOffset + aLength)) == 0)) michael@0: { michael@0: NS_WARN_IF_FALSE(channelContentLength != -1, michael@0: "nsWebBrowserPersist::OnDataAvailable() no content length " michael@0: "header, pushing what we have"); michael@0: // we're done with this pass; see if we need to do upload michael@0: nsAutoCString contentType; michael@0: channel->GetContentType(contentType); michael@0: // if we don't have the right type of output stream then it's a local file michael@0: nsCOMPtr storStream(do_QueryInterface(data->mStream)); michael@0: if (storStream) michael@0: { michael@0: data->mStream->Close(); michael@0: data->mStream = nullptr; // null out stream so we don't close it later michael@0: rv = StartUpload(storStream, data->mFile, contentType); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: readError = false; michael@0: cancel = true; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Notify listener if an error occurred. michael@0: if (cancel) michael@0: { michael@0: SendErrorStatusChange(readError, rv, michael@0: readError ? request : nullptr, data->mFile); michael@0: } michael@0: } michael@0: michael@0: // Cancel reading? michael@0: if (cancel) michael@0: { michael@0: EndDownload(NS_BINDING_ABORTED); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist::nsIProgressEventSink michael@0: //***************************************************************************** michael@0: michael@0: /* void onProgress (in nsIRequest request, in nsISupports ctxt, michael@0: in unsigned long long aProgress, in unsigned long long aProgressMax); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::OnProgress( michael@0: nsIRequest *request, nsISupports *ctxt, uint64_t aProgress, michael@0: uint64_t aProgressMax) michael@0: { michael@0: if (!mProgressListener) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Store the progress of this request michael@0: nsCOMPtr keyPtr = do_QueryInterface(request); michael@0: OutputData *data = mOutputMap.Get(keyPtr); michael@0: if (data) michael@0: { michael@0: data->mSelfProgress = int64_t(aProgress); michael@0: data->mSelfProgressMax = int64_t(aProgressMax); michael@0: } michael@0: else michael@0: { michael@0: UploadData *upData = mUploadList.Get(keyPtr); michael@0: if (upData) michael@0: { michael@0: upData->mSelfProgress = int64_t(aProgress); michael@0: upData->mSelfProgressMax = int64_t(aProgressMax); michael@0: } michael@0: } michael@0: michael@0: // Notify listener of total progress michael@0: CalcTotalProgress(); michael@0: if (mProgressListener2) michael@0: { michael@0: mProgressListener2->OnProgressChange64(nullptr, request, aProgress, michael@0: aProgressMax, mTotalCurrentProgress, mTotalMaxProgress); michael@0: } michael@0: else michael@0: { michael@0: // have to truncate 64-bit to 32bit michael@0: mProgressListener->OnProgressChange(nullptr, request, uint64_t(aProgress), michael@0: uint64_t(aProgressMax), mTotalCurrentProgress, mTotalMaxProgress); michael@0: } michael@0: michael@0: // If our progress listener implements nsIProgressEventSink, michael@0: // forward the notification michael@0: if (mEventSink) michael@0: { michael@0: mEventSink->OnProgress(request, ctxt, aProgress, aProgressMax); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: /* void onStatus (in nsIRequest request, in nsISupports ctxt, michael@0: in nsresult status, in wstring statusArg); */ michael@0: NS_IMETHODIMP nsWebBrowserPersist::OnStatus( michael@0: nsIRequest *request, nsISupports *ctxt, nsresult status, michael@0: const char16_t *statusArg) michael@0: { michael@0: if (mProgressListener) michael@0: { michael@0: // We need to filter out non-error error codes. michael@0: // Is the only NS_SUCCEEDED value NS_OK? michael@0: switch ( status ) michael@0: { michael@0: case NS_NET_STATUS_RESOLVING_HOST: michael@0: case NS_NET_STATUS_RESOLVED_HOST: michael@0: case NS_NET_STATUS_BEGIN_FTP_TRANSACTION: michael@0: case NS_NET_STATUS_END_FTP_TRANSACTION: michael@0: case NS_NET_STATUS_CONNECTING_TO: michael@0: case NS_NET_STATUS_CONNECTED_TO: michael@0: case NS_NET_STATUS_SENDING_TO: michael@0: case NS_NET_STATUS_RECEIVING_FROM: michael@0: case NS_NET_STATUS_WAITING_FOR: michael@0: case NS_NET_STATUS_READING: michael@0: case NS_NET_STATUS_WRITING: michael@0: break; michael@0: michael@0: default: michael@0: // Pass other notifications (for legitimate errors) along. michael@0: mProgressListener->OnStatusChange(nullptr, request, status, statusArg); michael@0: break; michael@0: } michael@0: michael@0: } michael@0: michael@0: // If our progress listener implements nsIProgressEventSink, michael@0: // forward the notification michael@0: if (mEventSink) michael@0: { michael@0: mEventSink->OnStatus(request, ctxt, status, statusArg); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: //***************************************************************************** michael@0: // nsWebBrowserPersist private methods michael@0: //***************************************************************************** michael@0: michael@0: // Convert error info into proper message text and send OnStatusChange notification michael@0: // to the web progress listener. michael@0: nsresult nsWebBrowserPersist::SendErrorStatusChange( michael@0: bool aIsReadError, nsresult aResult, nsIRequest *aRequest, nsIURI *aURI) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: if (!mProgressListener) michael@0: { michael@0: // Do nothing michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Get the file path or spec from the supplied URI michael@0: nsCOMPtr file; michael@0: GetLocalFileFromURI(aURI, getter_AddRefs(file)); michael@0: nsAutoString path; michael@0: if (file) michael@0: { michael@0: file->GetPath(path); michael@0: } michael@0: else michael@0: { michael@0: nsAutoCString fileurl; michael@0: aURI->GetSpec(fileurl); michael@0: AppendUTF8toUTF16(fileurl, path); michael@0: } michael@0: michael@0: nsAutoString msgId; michael@0: switch(aResult) michael@0: { michael@0: case NS_ERROR_FILE_NAME_TOO_LONG: michael@0: // File name too long. michael@0: msgId.AssignLiteral("fileNameTooLongError"); michael@0: break; michael@0: case NS_ERROR_FILE_ALREADY_EXISTS: michael@0: // File exists with same name as directory. michael@0: msgId.AssignLiteral("fileAlreadyExistsError"); michael@0: break; michael@0: case NS_ERROR_FILE_DISK_FULL: michael@0: case NS_ERROR_FILE_NO_DEVICE_SPACE: michael@0: // Out of space on target volume. michael@0: msgId.AssignLiteral("diskFull"); michael@0: break; michael@0: michael@0: case NS_ERROR_FILE_READ_ONLY: michael@0: // Attempt to write to read/only file. michael@0: msgId.AssignLiteral("readOnly"); michael@0: break; michael@0: michael@0: case NS_ERROR_FILE_ACCESS_DENIED: michael@0: // Attempt to write without sufficient permissions. michael@0: msgId.AssignLiteral("accessError"); michael@0: break; michael@0: michael@0: default: michael@0: // Generic read/write error message. michael@0: if (aIsReadError) michael@0: msgId.AssignLiteral("readError"); michael@0: else michael@0: msgId.AssignLiteral("writeError"); michael@0: break; michael@0: } michael@0: // Get properties file bundle and extract status string. michael@0: nsresult rv; michael@0: nsCOMPtr s = do_GetService(NS_STRINGBUNDLE_CONTRACTID, &rv); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && s, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr bundle; michael@0: rv = s->CreateBundle(kWebBrowserPersistStringBundle, getter_AddRefs(bundle)); michael@0: NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && bundle, NS_ERROR_FAILURE); michael@0: michael@0: nsXPIDLString msgText; michael@0: const char16_t *strings[1]; michael@0: strings[0] = path.get(); michael@0: rv = bundle->FormatStringFromName(msgId.get(), strings, 1, getter_Copies(msgText)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: mProgressListener->OnStatusChange(nullptr, aRequest, aResult, msgText); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::GetValidURIFromObject(nsISupports *aObject, nsIURI **aURI) const michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aObject); michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: nsCOMPtr objAsFile = do_QueryInterface(aObject); michael@0: if (objAsFile) michael@0: { michael@0: return NS_NewFileURI(aURI, objAsFile); michael@0: } michael@0: nsCOMPtr objAsURI = do_QueryInterface(aObject); michael@0: if (objAsURI) michael@0: { michael@0: *aURI = objAsURI; michael@0: NS_ADDREF(*aURI); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::GetLocalFileFromURI(nsIURI *aURI, nsIFile **aLocalFile) const michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr fileURL = do_QueryInterface(aURI, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: michael@0: nsCOMPtr file; michael@0: rv = fileURL->GetFile(getter_AddRefs(file)); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: file.forget(aLocalFile); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::AppendPathToURI(nsIURI *aURI, const nsAString & aPath) const michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: nsAutoCString newPath; michael@0: nsresult rv = aURI->GetPath(newPath); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // Append a forward slash if necessary michael@0: int32_t len = newPath.Length(); michael@0: if (len > 0 && newPath.CharAt(len - 1) != '/') michael@0: { michael@0: newPath.Append('/'); michael@0: } michael@0: michael@0: // Store the path back on the URI michael@0: AppendUTF16toUTF8(aPath, newPath); michael@0: aURI->SetPath(newPath); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::SaveURIInternal( michael@0: nsIURI *aURI, nsISupports *aCacheKey, nsIURI *aReferrer, michael@0: nsIInputStream *aPostData, const char *aExtraHeaders, michael@0: nsIURI *aFile, bool aCalcFileExt, bool aIsPrivate) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: michael@0: nsresult rv = NS_OK; michael@0: michael@0: mURI = aURI; michael@0: michael@0: nsLoadFlags loadFlags = nsIRequest::LOAD_NORMAL; michael@0: if (mPersistFlags & PERSIST_FLAGS_BYPASS_CACHE) michael@0: { michael@0: loadFlags |= nsIRequest::LOAD_BYPASS_CACHE; michael@0: } michael@0: else if (mPersistFlags & PERSIST_FLAGS_FROM_CACHE) michael@0: { michael@0: loadFlags |= nsIRequest::LOAD_FROM_CACHE; michael@0: } michael@0: michael@0: // Extract the cache key michael@0: nsCOMPtr cacheKey; michael@0: if (aCacheKey) michael@0: { michael@0: // Test if the cache key is actually a web page descriptor (docshell) michael@0: // or session history entry. michael@0: nsCOMPtr shEntry = do_QueryInterface(aCacheKey); michael@0: if (!shEntry) michael@0: { michael@0: nsCOMPtr webPageDescriptor = michael@0: do_QueryInterface(aCacheKey); michael@0: if (webPageDescriptor) michael@0: { michael@0: nsCOMPtr currentDescriptor; michael@0: webPageDescriptor->GetCurrentDescriptor(getter_AddRefs(currentDescriptor)); michael@0: shEntry = do_QueryInterface(currentDescriptor); michael@0: } michael@0: } michael@0: michael@0: if (shEntry) michael@0: { michael@0: shEntry->GetCacheKey(getter_AddRefs(cacheKey)); michael@0: } michael@0: else michael@0: { michael@0: // Assume a plain cache key michael@0: cacheKey = aCacheKey; michael@0: } michael@0: } michael@0: michael@0: // Open a channel to the URI michael@0: nsCOMPtr inputChannel; michael@0: rv = NS_NewChannel(getter_AddRefs(inputChannel), aURI, michael@0: nullptr, nullptr, static_cast(this), michael@0: loadFlags); michael@0: michael@0: nsCOMPtr pbChannel = do_QueryInterface(inputChannel); michael@0: if (pbChannel) michael@0: { michael@0: pbChannel->SetPrivate(aIsPrivate); michael@0: } michael@0: michael@0: if (NS_FAILED(rv) || inputChannel == nullptr) michael@0: { michael@0: EndDownload(NS_ERROR_FAILURE); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Disable content conversion michael@0: if (mPersistFlags & PERSIST_FLAGS_NO_CONVERSION) michael@0: { michael@0: nsCOMPtr encodedChannel(do_QueryInterface(inputChannel)); michael@0: if (encodedChannel) michael@0: { michael@0: encodedChannel->SetApplyConversion(false); michael@0: } michael@0: } michael@0: michael@0: if (mPersistFlags & PERSIST_FLAGS_FORCE_ALLOW_COOKIES) michael@0: { michael@0: nsCOMPtr httpChannelInternal = michael@0: do_QueryInterface(inputChannel); michael@0: if (httpChannelInternal) michael@0: httpChannelInternal->SetForceAllowThirdPartyCookie(true); michael@0: } michael@0: michael@0: // Set the referrer, post data and headers if any michael@0: nsCOMPtr httpChannel(do_QueryInterface(inputChannel)); michael@0: if (httpChannel) michael@0: { michael@0: // Referrer michael@0: if (aReferrer) michael@0: { michael@0: httpChannel->SetReferrer(aReferrer); michael@0: } michael@0: michael@0: // Post data michael@0: if (aPostData) michael@0: { michael@0: nsCOMPtr stream(do_QueryInterface(aPostData)); michael@0: if (stream) michael@0: { michael@0: // Rewind the postdata stream michael@0: stream->Seek(nsISeekableStream::NS_SEEK_SET, 0); michael@0: nsCOMPtr uploadChannel(do_QueryInterface(httpChannel)); michael@0: NS_ASSERTION(uploadChannel, "http must support nsIUploadChannel"); michael@0: // Attach the postdata to the http channel michael@0: uploadChannel->SetUploadStream(aPostData, EmptyCString(), -1); michael@0: } michael@0: } michael@0: michael@0: // Cache key michael@0: nsCOMPtr cacheChannel(do_QueryInterface(httpChannel)); michael@0: if (cacheChannel && cacheKey) michael@0: { michael@0: cacheChannel->SetCacheKey(cacheKey); michael@0: } michael@0: michael@0: // Headers michael@0: if (aExtraHeaders) michael@0: { michael@0: nsAutoCString oneHeader; michael@0: nsAutoCString headerName; michael@0: nsAutoCString headerValue; michael@0: int32_t crlf = 0; michael@0: int32_t colon = 0; michael@0: const char *kWhitespace = "\b\t\r\n "; michael@0: nsAutoCString extraHeaders(aExtraHeaders); michael@0: while (true) michael@0: { michael@0: crlf = extraHeaders.Find("\r\n", true); michael@0: if (crlf == -1) michael@0: break; michael@0: extraHeaders.Mid(oneHeader, 0, crlf); michael@0: extraHeaders.Cut(0, crlf + 2); michael@0: colon = oneHeader.Find(":"); michael@0: if (colon == -1) michael@0: break; // Should have a colon michael@0: oneHeader.Left(headerName, colon); michael@0: colon++; michael@0: oneHeader.Mid(headerValue, colon, oneHeader.Length() - colon); michael@0: headerName.Trim(kWhitespace); michael@0: headerValue.Trim(kWhitespace); michael@0: // Add the header (merging if required) michael@0: rv = httpChannel->SetRequestHeader(headerName, headerValue, true); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: EndDownload(NS_ERROR_FAILURE); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: return SaveChannelInternal(inputChannel, aFile, aCalcFileExt); michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::SaveChannelInternal( michael@0: nsIChannel *aChannel, nsIURI *aFile, bool aCalcFileExt) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aChannel); michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: michael@0: // The default behaviour of SaveChannelInternal is to download the source michael@0: // into a storage stream and upload that to the target. MakeOutputStream michael@0: // special-cases a file target and creates a file output stream directly. michael@0: // We want to special-case a file source and create a file input stream, michael@0: // but we don't need to do this in the case of a file target. michael@0: nsCOMPtr fc(do_QueryInterface(aChannel)); michael@0: nsCOMPtr fu(do_QueryInterface(aFile)); michael@0: if (fc && !fu) { michael@0: nsCOMPtr fileInputStream, bufferedInputStream; michael@0: nsresult rv = aChannel->Open(getter_AddRefs(fileInputStream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = NS_NewBufferedInputStream(getter_AddRefs(bufferedInputStream), michael@0: fileInputStream, BUFFERED_OUTPUT_SIZE); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsAutoCString contentType; michael@0: aChannel->GetContentType(contentType); michael@0: return StartUpload(bufferedInputStream, aFile, contentType); michael@0: } michael@0: michael@0: // Read from the input channel michael@0: nsresult rv = aChannel->AsyncOpen(this, nullptr); michael@0: if (rv == NS_ERROR_NO_CONTENT) michael@0: { michael@0: // Assume this is a protocol such as mailto: which does not feed out michael@0: // data and just ignore it. michael@0: return NS_SUCCESS_DONT_FIXUP; michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: // Opening failed, but do we care? michael@0: if (mPersistFlags & PERSIST_FLAGS_FAIL_ON_BROKEN_LINKS) michael@0: { michael@0: SendErrorStatusChange(true, rv, aChannel, aFile); michael@0: EndDownload(NS_ERROR_FAILURE); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: return NS_SUCCESS_DONT_FIXUP; michael@0: } michael@0: michael@0: // Add the output transport to the output map with the channel as the key michael@0: nsCOMPtr keyPtr = do_QueryInterface(aChannel); michael@0: mOutputMap.Put(keyPtr, new OutputData(aFile, mURI, aCalcFileExt)); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::GetExtensionForContentType(const char16_t *aContentType, char16_t **aExt) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aContentType); michael@0: NS_ENSURE_ARG_POINTER(aExt); michael@0: michael@0: *aExt = nullptr; michael@0: michael@0: nsresult rv; michael@0: if (!mMIMEService) michael@0: { michael@0: mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: nsCOMPtr mimeInfo; michael@0: nsAutoCString contentType; michael@0: contentType.AssignWithConversion(aContentType); michael@0: nsAutoCString ext; michael@0: rv = mMIMEService->GetPrimaryExtension(contentType, EmptyCString(), ext); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: *aExt = UTF8ToNewUnicode(ext); michael@0: NS_ENSURE_TRUE(*aExt, NS_ERROR_OUT_OF_MEMORY); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::GetDocumentExtension(nsIDOMDocument *aDocument, char16_t **aExt) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDocument); michael@0: NS_ENSURE_ARG_POINTER(aExt); michael@0: michael@0: nsXPIDLString contentType; michael@0: nsresult rv = GetDocEncoderContentType(aDocument, nullptr, getter_Copies(contentType)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: return GetExtensionForContentType(contentType.get(), aExt); michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::GetDocEncoderContentType(nsIDOMDocument *aDocument, const char16_t *aContentType, char16_t **aRealContentType) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDocument); michael@0: NS_ENSURE_ARG_POINTER(aRealContentType); michael@0: michael@0: *aRealContentType = nullptr; michael@0: michael@0: nsAutoString defaultContentType(NS_LITERAL_STRING("text/html")); michael@0: michael@0: // Get the desired content type for the document, either by using the one michael@0: // supplied or from the document itself. michael@0: michael@0: nsAutoString contentType; michael@0: if (aContentType) michael@0: { michael@0: contentType.Assign(aContentType); michael@0: } michael@0: else michael@0: { michael@0: // Get the content type from the document michael@0: nsAutoString type; michael@0: if (NS_SUCCEEDED(aDocument->GetContentType(type)) && !type.IsEmpty()) michael@0: contentType.Assign(type); michael@0: } michael@0: michael@0: // Check that an encoder actually exists for the desired output type. The michael@0: // following content types will usually yield an encoder. michael@0: // michael@0: // text/xml michael@0: // application/xml michael@0: // application/xhtml+xml michael@0: // image/svg+xml michael@0: // text/html michael@0: // text/plain michael@0: michael@0: if (!contentType.IsEmpty() && michael@0: !contentType.Equals(defaultContentType, nsCaseInsensitiveStringComparator())) michael@0: { michael@0: // Check if there is an encoder for the desired content type michael@0: nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); michael@0: AppendUTF16toUTF8(contentType, contractID); michael@0: michael@0: nsCOMPtr registrar; michael@0: NS_GetComponentRegistrar(getter_AddRefs(registrar)); michael@0: if (registrar) michael@0: { michael@0: bool result; michael@0: nsresult rv = registrar->IsContractIDRegistered(contractID.get(), &result); michael@0: if (NS_SUCCEEDED(rv) && result) michael@0: { michael@0: *aRealContentType = ToNewUnicode(contentType); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Use the default if no encoder exists for the desired one michael@0: if (!*aRealContentType) michael@0: { michael@0: *aRealContentType = ToNewUnicode(defaultContentType); michael@0: } michael@0: michael@0: NS_ENSURE_TRUE(*aRealContentType, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::SaveDocumentInternal( michael@0: nsIDOMDocument *aDocument, nsIURI *aFile, nsIURI *aDataPath) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aDocument); michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: michael@0: // See if we can get the local file representation of this URI michael@0: nsCOMPtr localFile; michael@0: nsresult rv = GetLocalFileFromURI(aFile, getter_AddRefs(localFile)); michael@0: michael@0: nsCOMPtr localDataPath; michael@0: if (NS_SUCCEEDED(rv) && aDataPath) michael@0: { michael@0: // See if we can get the local file representation of this URI michael@0: rv = GetLocalFileFromURI(aDataPath, getter_AddRefs(localDataPath)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: nsCOMPtr docAsNode = do_QueryInterface(aDocument); michael@0: michael@0: // Persist the main document michael@0: nsCOMPtr doc(do_QueryInterface(aDocument)); michael@0: if (!doc) { michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: mURI = doc->GetDocumentURI(); michael@0: michael@0: nsCOMPtr oldBaseURI = mCurrentBaseURI; michael@0: nsAutoCString oldCharset(mCurrentCharset); michael@0: michael@0: // Store the base URI and the charset michael@0: mCurrentBaseURI = doc->GetBaseURI(); michael@0: mCurrentCharset = doc->GetDocumentCharacterSet(); michael@0: michael@0: // Does the caller want to fixup the referenced URIs and save those too? michael@0: if (aDataPath) michael@0: { michael@0: // Basic steps are these. michael@0: // michael@0: // 1. Iterate through the document (and subdocuments) building a list michael@0: // of unique URIs. michael@0: // 2. For each URI create an OutputData entry and open a channel to save michael@0: // it. As each URI is saved, discover the mime type and fix up the michael@0: // local filename with the correct extension. michael@0: // 3. Store the document in a list and wait for URI persistence to finish michael@0: // 4. After URI persistence completes save the list of documents, michael@0: // fixing it up as it goes out to file. michael@0: michael@0: nsCOMPtr oldDataPath = mCurrentDataPath; michael@0: bool oldDataPathIsRelative = mCurrentDataPathIsRelative; michael@0: nsCString oldCurrentRelativePathToData = mCurrentRelativePathToData; michael@0: uint32_t oldThingsToPersist = mCurrentThingsToPersist; michael@0: michael@0: mCurrentDataPathIsRelative = false; michael@0: mCurrentDataPath = aDataPath; michael@0: mCurrentRelativePathToData = ""; michael@0: mCurrentThingsToPersist = 0; michael@0: michael@0: // Determine if the specified data path is relative to the michael@0: // specified file, (e.g. c:\docs\htmldata is relative to michael@0: // c:\docs\myfile.htm, but not to d:\foo\data. michael@0: michael@0: // Starting with the data dir work back through its parents michael@0: // checking if one of them matches the base directory. michael@0: michael@0: if (localDataPath && localFile) michael@0: { michael@0: nsCOMPtr baseDir; michael@0: localFile->GetParent(getter_AddRefs(baseDir)); michael@0: michael@0: nsAutoCString relativePathToData; michael@0: nsCOMPtr dataDirParent; michael@0: dataDirParent = localDataPath; michael@0: while (dataDirParent) michael@0: { michael@0: bool sameDir = false; michael@0: dataDirParent->Equals(baseDir, &sameDir); michael@0: if (sameDir) michael@0: { michael@0: mCurrentRelativePathToData = relativePathToData; michael@0: mCurrentDataPathIsRelative = true; michael@0: break; michael@0: } michael@0: michael@0: nsAutoString dirName; michael@0: dataDirParent->GetLeafName(dirName); michael@0: michael@0: nsAutoCString newRelativePathToData; michael@0: newRelativePathToData = NS_ConvertUTF16toUTF8(dirName) michael@0: + NS_LITERAL_CSTRING("/") michael@0: + relativePathToData; michael@0: relativePathToData = newRelativePathToData; michael@0: michael@0: nsCOMPtr newDataDirParent; michael@0: rv = dataDirParent->GetParent(getter_AddRefs(newDataDirParent)); michael@0: dataDirParent = newDataDirParent; michael@0: } michael@0: } michael@0: else michael@0: { michael@0: // generate a relative path if possible michael@0: nsCOMPtr pathToBaseURL(do_QueryInterface(aFile)); michael@0: if (pathToBaseURL) michael@0: { michael@0: nsAutoCString relativePath; // nsACString michael@0: if (NS_SUCCEEDED(pathToBaseURL->GetRelativeSpec(aDataPath, relativePath))) michael@0: { michael@0: mCurrentDataPathIsRelative = true; michael@0: mCurrentRelativePathToData = relativePath; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Store the document in a list so when URI persistence is done and the michael@0: // filenames of saved URIs are known, the documents can be fixed up and michael@0: // saved michael@0: michael@0: DocData *docData = new DocData; michael@0: docData->mBaseURI = mCurrentBaseURI; michael@0: docData->mCharset = mCurrentCharset; michael@0: docData->mDocument = aDocument; michael@0: docData->mFile = aFile; michael@0: docData->mRelativePathToData = mCurrentRelativePathToData; michael@0: docData->mDataPath = mCurrentDataPath; michael@0: docData->mDataPathIsRelative = mCurrentDataPathIsRelative; michael@0: mDocList.AppendElement(docData); michael@0: michael@0: // Walk the DOM gathering a list of externally referenced URIs in the uri map michael@0: nsCOMPtr walker; michael@0: rv = aDocument->CreateTreeWalker(docAsNode, michael@0: nsIDOMNodeFilter::SHOW_ELEMENT | michael@0: nsIDOMNodeFilter::SHOW_DOCUMENT | michael@0: nsIDOMNodeFilter::SHOW_PROCESSING_INSTRUCTION, michael@0: nullptr, 1, getter_AddRefs(walker)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: nsCOMPtr currentNode; michael@0: walker->GetCurrentNode(getter_AddRefs(currentNode)); michael@0: while (currentNode) michael@0: { michael@0: OnWalkDOMNode(currentNode); michael@0: walker->NextNode(getter_AddRefs(currentNode)); michael@0: } michael@0: michael@0: // If there are things to persist, create a directory to hold them michael@0: if (mCurrentThingsToPersist > 0) michael@0: { michael@0: if (localDataPath) michael@0: { michael@0: bool exists = false; michael@0: bool haveDir = false; michael@0: michael@0: localDataPath->Exists(&exists); michael@0: if (exists) michael@0: { michael@0: localDataPath->IsDirectory(&haveDir); michael@0: } michael@0: if (!haveDir) michael@0: { michael@0: rv = localDataPath->Create(nsIFile::DIRECTORY_TYPE, 0755); michael@0: if (NS_SUCCEEDED(rv)) michael@0: haveDir = true; michael@0: else michael@0: SendErrorStatusChange(false, rv, nullptr, aFile); michael@0: } michael@0: if (!haveDir) michael@0: { michael@0: EndDownload(NS_ERROR_FAILURE); michael@0: mCurrentBaseURI = oldBaseURI; michael@0: mCurrentCharset = oldCharset; michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) michael@0: { michael@0: // Add to list of things to delete later if all goes wrong michael@0: CleanupData *cleanupData = new CleanupData; michael@0: NS_ENSURE_TRUE(cleanupData, NS_ERROR_OUT_OF_MEMORY); michael@0: cleanupData->mFile = localDataPath; michael@0: cleanupData->mIsDirectory = true; michael@0: mCleanupList.AppendElement(cleanupData); michael@0: } michael@0: } michael@0: } michael@0: michael@0: mCurrentThingsToPersist = oldThingsToPersist; michael@0: mCurrentDataPath = oldDataPath; michael@0: mCurrentDataPathIsRelative = oldDataPathIsRelative; michael@0: mCurrentRelativePathToData = oldCurrentRelativePathToData; michael@0: } michael@0: else michael@0: { michael@0: // Set the document base to ensure relative links still work michael@0: SetDocumentBase(aDocument, mCurrentBaseURI); michael@0: michael@0: // Get the content type to save with michael@0: nsXPIDLString realContentType; michael@0: GetDocEncoderContentType(aDocument, michael@0: !mContentType.IsEmpty() ? mContentType.get() : nullptr, michael@0: getter_Copies(realContentType)); michael@0: michael@0: nsAutoCString contentType; contentType.AssignWithConversion(realContentType); michael@0: nsAutoCString charType; // Empty michael@0: michael@0: // Save the document michael@0: rv = SaveDocumentWithFixup( michael@0: aDocument, michael@0: nullptr, // no dom fixup michael@0: aFile, michael@0: mReplaceExisting, michael@0: contentType, michael@0: charType, michael@0: mEncodingFlags); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: mCurrentBaseURI = oldBaseURI; michael@0: mCurrentCharset = oldCharset; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::SaveDocuments() michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: mStartSaving = true; michael@0: michael@0: // Iterate through all queued documents, saving them to file and fixing michael@0: // them up on the way. michael@0: michael@0: uint32_t i; michael@0: for (i = 0; i < mDocList.Length(); i++) michael@0: { michael@0: DocData *docData = mDocList.ElementAt(i); michael@0: if (!docData) michael@0: { michael@0: rv = NS_ERROR_FAILURE; michael@0: break; michael@0: } michael@0: michael@0: mCurrentBaseURI = docData->mBaseURI; michael@0: mCurrentCharset = docData->mCharset; michael@0: michael@0: // Save the document, fixing it up with the new URIs as we do michael@0: michael@0: nsEncoderNodeFixup *nodeFixup; michael@0: nodeFixup = new nsEncoderNodeFixup; michael@0: if (nodeFixup) michael@0: nodeFixup->mWebBrowserPersist = this; michael@0: michael@0: // Get the content type michael@0: nsXPIDLString realContentType; michael@0: GetDocEncoderContentType(docData->mDocument, michael@0: !mContentType.IsEmpty() ? mContentType.get() : nullptr, michael@0: getter_Copies(realContentType)); michael@0: michael@0: nsAutoCString contentType; contentType.AssignWithConversion(realContentType.get()); michael@0: nsAutoCString charType; // Empty michael@0: michael@0: // Save the document, fixing up the links as it goes out michael@0: rv = SaveDocumentWithFixup( michael@0: docData->mDocument, michael@0: nodeFixup, michael@0: docData->mFile, michael@0: mReplaceExisting, michael@0: contentType, michael@0: charType, michael@0: mEncodingFlags); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: break; michael@0: michael@0: // if we're serializing, bail after first iteration of loop michael@0: if (mSerializingOutput) michael@0: break; michael@0: } michael@0: michael@0: // delete, cleanup regardless of errors (bug 132417) michael@0: for (i = 0; i < mDocList.Length(); i++) michael@0: { michael@0: DocData *docData = mDocList.ElementAt(i); michael@0: delete docData; michael@0: if (mSerializingOutput) michael@0: { michael@0: mDocList.RemoveElementAt(i); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (!mSerializingOutput) michael@0: { michael@0: mDocList.Clear(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void nsWebBrowserPersist::Cleanup() michael@0: { michael@0: mURIMap.Clear(); michael@0: mOutputMap.EnumerateRead(EnumCleanupOutputMap, this); michael@0: mOutputMap.Clear(); michael@0: mUploadList.EnumerateRead(EnumCleanupUploadList, this); michael@0: mUploadList.Clear(); michael@0: uint32_t i; michael@0: for (i = 0; i < mDocList.Length(); i++) michael@0: { michael@0: DocData *docData = mDocList.ElementAt(i); michael@0: delete docData; michael@0: } michael@0: mDocList.Clear(); michael@0: for (i = 0; i < mCleanupList.Length(); i++) michael@0: { michael@0: CleanupData *cleanupData = mCleanupList.ElementAt(i); michael@0: delete cleanupData; michael@0: } michael@0: mCleanupList.Clear(); michael@0: mFilenameList.Clear(); michael@0: } michael@0: michael@0: void nsWebBrowserPersist::CleanupLocalFiles() michael@0: { michael@0: // Two passes, the first pass cleans up files, the second pass tests michael@0: // for and then deletes empty directories. Directories that are not michael@0: // empty after the first pass must contain files from something else michael@0: // and are not deleted. michael@0: int pass; michael@0: for (pass = 0; pass < 2; pass++) michael@0: { michael@0: uint32_t i; michael@0: for (i = 0; i < mCleanupList.Length(); i++) michael@0: { michael@0: CleanupData *cleanupData = mCleanupList.ElementAt(i); michael@0: nsCOMPtr file = cleanupData->mFile; michael@0: michael@0: // Test if the dir / file exists (something in an earlier loop michael@0: // may have already removed it) michael@0: bool exists = false; michael@0: file->Exists(&exists); michael@0: if (!exists) michael@0: continue; michael@0: michael@0: // Test if the file has changed in between creation and deletion michael@0: // in some way that means it should be ignored michael@0: bool isDirectory = false; michael@0: file->IsDirectory(&isDirectory); michael@0: if (isDirectory != cleanupData->mIsDirectory) michael@0: continue; // A file has become a dir or vice versa ! michael@0: michael@0: if (pass == 0 && !isDirectory) michael@0: { michael@0: file->Remove(false); michael@0: } michael@0: else if (pass == 1 && isDirectory) // Directory michael@0: { michael@0: // Directories are more complicated. Enumerate through michael@0: // children looking for files. Any files created by the michael@0: // persist object would have been deleted by the first michael@0: // pass so if there are any there at this stage, the dir michael@0: // cannot be deleted because it has someone else's files michael@0: // in it. Empty child dirs are deleted but they must be michael@0: // recursed through to ensure they are actually empty. michael@0: michael@0: bool isEmptyDirectory = true; michael@0: nsCOMArray dirStack; michael@0: int32_t stackSize = 0; michael@0: michael@0: // Push the top level enum onto the stack michael@0: nsCOMPtr pos; michael@0: if (NS_SUCCEEDED(file->GetDirectoryEntries(getter_AddRefs(pos)))) michael@0: dirStack.AppendObject(pos); michael@0: michael@0: while (isEmptyDirectory && (stackSize = dirStack.Count())) michael@0: { michael@0: // Pop the last element michael@0: nsCOMPtr curPos; michael@0: curPos = dirStack[stackSize-1]; michael@0: dirStack.RemoveObjectAt(stackSize - 1); michael@0: michael@0: // Test if the enumerator has any more files in it michael@0: bool hasMoreElements = false; michael@0: curPos->HasMoreElements(&hasMoreElements); michael@0: if (!hasMoreElements) michael@0: { michael@0: continue; michael@0: } michael@0: michael@0: // Child files automatically make this code drop out, michael@0: // while child dirs keep the loop going. michael@0: nsCOMPtr child; michael@0: curPos->GetNext(getter_AddRefs(child)); michael@0: NS_ASSERTION(child, "No child element, but hasMoreElements says otherwise"); michael@0: if (!child) michael@0: continue; michael@0: nsCOMPtr childAsFile = do_QueryInterface(child); michael@0: NS_ASSERTION(childAsFile, "This should be a file but isn't"); michael@0: michael@0: bool childIsSymlink = false; michael@0: childAsFile->IsSymlink(&childIsSymlink); michael@0: bool childIsDir = false; michael@0: childAsFile->IsDirectory(&childIsDir); michael@0: if (!childIsDir || childIsSymlink) michael@0: { michael@0: // Some kind of file or symlink which means dir michael@0: // is not empty so just drop out. michael@0: isEmptyDirectory = false; michael@0: break; michael@0: } michael@0: // Push parent enumerator followed by child enumerator michael@0: nsCOMPtr childPos; michael@0: childAsFile->GetDirectoryEntries(getter_AddRefs(childPos)); michael@0: dirStack.AppendObject(curPos); michael@0: if (childPos) michael@0: dirStack.AppendObject(childPos); michael@0: michael@0: } michael@0: dirStack.Clear(); michael@0: michael@0: // If after all that walking the dir is deemed empty, delete it michael@0: if (isEmptyDirectory) michael@0: { michael@0: file->Remove(true); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::CalculateUniqueFilename(nsIURI *aURI) michael@0: { michael@0: nsCOMPtr url(do_QueryInterface(aURI)); michael@0: NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); michael@0: michael@0: bool nameHasChanged = false; michael@0: nsresult rv; michael@0: michael@0: // Get the old filename michael@0: nsAutoCString filename; michael@0: rv = url->GetFileName(filename); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: nsAutoCString directory; michael@0: rv = url->GetDirectory(directory); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // Split the filename into a base and an extension. michael@0: // e.g. "foo.html" becomes "foo" & ".html" michael@0: // michael@0: // The nsIURL methods GetFileBaseName & GetFileExtension don't michael@0: // preserve the dot whereas this code does to save some effort michael@0: // later when everything is put back together. michael@0: int32_t lastDot = filename.RFind("."); michael@0: nsAutoCString base; michael@0: nsAutoCString ext; michael@0: if (lastDot >= 0) michael@0: { michael@0: filename.Mid(base, 0, lastDot); michael@0: filename.Mid(ext, lastDot, filename.Length() - lastDot); // includes dot michael@0: } michael@0: else michael@0: { michael@0: // filename contains no dot michael@0: base = filename; michael@0: } michael@0: michael@0: // Test if the filename is longer than allowed by the OS michael@0: int32_t needToChop = filename.Length() - kDefaultMaxFilenameLength; michael@0: if (needToChop > 0) michael@0: { michael@0: // Truncate the base first and then the ext if necessary michael@0: if (base.Length() > (uint32_t) needToChop) michael@0: { michael@0: base.Truncate(base.Length() - needToChop); michael@0: } michael@0: else michael@0: { michael@0: needToChop -= base.Length() - 1; michael@0: base.Truncate(1); michael@0: if (ext.Length() > (uint32_t) needToChop) michael@0: { michael@0: ext.Truncate(ext.Length() - needToChop); michael@0: } michael@0: else michael@0: { michael@0: ext.Truncate(0); michael@0: } michael@0: // If kDefaultMaxFilenameLength were 1 we'd be in trouble here, michael@0: // but that won't happen because it will be set to a sensible michael@0: // value. michael@0: } michael@0: michael@0: filename.Assign(base); michael@0: filename.Append(ext); michael@0: nameHasChanged = true; michael@0: } michael@0: michael@0: // Ensure the filename is unique michael@0: // Create a filename if it's empty, or if the filename / datapath is michael@0: // already taken by another URI and create an alternate name. michael@0: michael@0: if (base.IsEmpty() || !mFilenameList.IsEmpty()) michael@0: { michael@0: nsAutoCString tmpPath; michael@0: nsAutoCString tmpBase; michael@0: uint32_t duplicateCounter = 1; michael@0: while (1) michael@0: { michael@0: // Make a file name, michael@0: // Foo become foo_001, foo_002, etc. michael@0: // Empty files become _001, _002 etc. michael@0: michael@0: if (base.IsEmpty() || duplicateCounter > 1) michael@0: { michael@0: char * tmp = PR_smprintf("_%03d", duplicateCounter); michael@0: NS_ENSURE_TRUE(tmp, NS_ERROR_OUT_OF_MEMORY); michael@0: if (filename.Length() < kDefaultMaxFilenameLength - 4) michael@0: { michael@0: tmpBase = base; michael@0: } michael@0: else michael@0: { michael@0: base.Mid(tmpBase, 0, base.Length() - 4); michael@0: } michael@0: tmpBase.Append(tmp); michael@0: PR_smprintf_free(tmp); michael@0: } michael@0: else michael@0: { michael@0: tmpBase = base; michael@0: } michael@0: michael@0: tmpPath.Assign(directory); michael@0: tmpPath.Append(tmpBase); michael@0: tmpPath.Append(ext); michael@0: michael@0: // Test if the name is a duplicate michael@0: if (!mFilenameList.Contains(tmpPath)) michael@0: { michael@0: if (!base.Equals(tmpBase)) michael@0: { michael@0: filename.Assign(tmpBase); michael@0: filename.Append(ext); michael@0: nameHasChanged = true; michael@0: } michael@0: break; michael@0: } michael@0: duplicateCounter++; michael@0: } michael@0: } michael@0: michael@0: // Add name to list of those already used michael@0: nsAutoCString newFilepath(directory); michael@0: newFilepath.Append(filename); michael@0: mFilenameList.AppendElement(newFilepath); michael@0: michael@0: // Update the uri accordingly if the filename actually changed michael@0: if (nameHasChanged) michael@0: { michael@0: // Final sanity test michael@0: if (filename.Length() > kDefaultMaxFilenameLength) michael@0: { michael@0: NS_WARNING("Filename wasn't truncated less than the max file length - how can that be?"); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsCOMPtr localFile; michael@0: GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); michael@0: michael@0: if (localFile) michael@0: { michael@0: nsAutoString filenameAsUnichar; michael@0: filenameAsUnichar.AssignWithConversion(filename.get()); michael@0: localFile->SetLeafName(filenameAsUnichar); michael@0: michael@0: // Resync the URI with the file after the extension has been appended michael@0: nsresult rv; michael@0: nsCOMPtr fileURL = do_QueryInterface(aURI, &rv); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: fileURL->SetFile(localFile); // this should recalculate uri michael@0: } michael@0: else michael@0: { michael@0: url->SetFileName(filename); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::MakeFilenameFromURI(nsIURI *aURI, nsString &aFilename) michael@0: { michael@0: // Try to get filename from the URI. michael@0: nsAutoString fileName; michael@0: michael@0: // Get a suggested file name from the URL but strip it of characters michael@0: // likely to cause the name to be illegal. michael@0: michael@0: nsCOMPtr url(do_QueryInterface(aURI)); michael@0: if (url) michael@0: { michael@0: nsAutoCString nameFromURL; michael@0: url->GetFileName(nameFromURL); michael@0: if (mPersistFlags & PERSIST_FLAGS_DONT_CHANGE_FILENAMES) michael@0: { michael@0: fileName.AssignWithConversion(NS_UnescapeURL(nameFromURL).get()); michael@0: goto end; michael@0: } michael@0: if (!nameFromURL.IsEmpty()) michael@0: { michael@0: // Unescape the file name (GetFileName escapes it) michael@0: NS_UnescapeURL(nameFromURL); michael@0: uint32_t nameLength = 0; michael@0: const char *p = nameFromURL.get(); michael@0: for (;*p && *p != ';' && *p != '?' && *p != '#' && *p != '.' michael@0: ;p++) michael@0: { michael@0: if (nsCRT::IsAsciiAlpha(*p) || nsCRT::IsAsciiDigit(*p) michael@0: || *p == '.' || *p == '-' || *p == '_' || (*p == ' ')) michael@0: { michael@0: fileName.Append(char16_t(*p)); michael@0: if (++nameLength == kDefaultMaxFilenameLength) michael@0: { michael@0: // Note: michael@0: // There is no point going any further since it will be michael@0: // truncated in CalculateUniqueFilename anyway. michael@0: // More importantly, certain implementations of michael@0: // nsIFile (e.g. the Mac impl) might truncate michael@0: // names in undesirable ways, such as truncating from michael@0: // the middle, inserting ellipsis and so on. michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Empty filenames can confuse the local file object later michael@0: // when it attempts to set the leaf name in CalculateUniqueFilename michael@0: // for duplicates and ends up replacing the parent dir. To avoid michael@0: // the problem, all filenames are made at least one character long. michael@0: if (fileName.IsEmpty()) michael@0: { michael@0: fileName.Append(char16_t('a')); // 'a' is for arbitrary michael@0: } michael@0: michael@0: end: michael@0: aFilename = fileName; michael@0: return NS_OK; michael@0: } michael@0: michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::CalculateAndAppendFileExt(nsIURI *aURI, nsIChannel *aChannel, nsIURI *aOriginalURIWithExtension) michael@0: { michael@0: nsresult rv; michael@0: michael@0: if (!mMIMEService) michael@0: { michael@0: mMIMEService = do_GetService(NS_MIMESERVICE_CONTRACTID, &rv); michael@0: NS_ENSURE_TRUE(mMIMEService, NS_ERROR_FAILURE); michael@0: } michael@0: michael@0: nsAutoCString contentType; michael@0: michael@0: // Get the content type from the channel michael@0: aChannel->GetContentType(contentType); michael@0: michael@0: // Get the content type from the MIME service michael@0: if (contentType.IsEmpty()) michael@0: { michael@0: nsCOMPtr uri; michael@0: aChannel->GetOriginalURI(getter_AddRefs(uri)); michael@0: mMIMEService->GetTypeFromURI(uri, contentType); michael@0: } michael@0: michael@0: // Append the extension onto the file michael@0: if (!contentType.IsEmpty()) michael@0: { michael@0: nsCOMPtr mimeInfo; michael@0: mMIMEService->GetFromTypeAndExtension( michael@0: contentType, EmptyCString(), getter_AddRefs(mimeInfo)); michael@0: michael@0: nsCOMPtr localFile; michael@0: GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); michael@0: michael@0: if (mimeInfo) michael@0: { michael@0: nsCOMPtr url(do_QueryInterface(aURI)); michael@0: NS_ENSURE_TRUE(url, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoCString newFileName; michael@0: url->GetFileName(newFileName); michael@0: michael@0: // Test if the current extension is current for the mime type michael@0: bool hasExtension = false; michael@0: int32_t ext = newFileName.RFind("."); michael@0: if (ext != -1) michael@0: { michael@0: mimeInfo->ExtensionExists(Substring(newFileName, ext + 1), &hasExtension); michael@0: } michael@0: michael@0: // Append the mime file extension michael@0: nsAutoCString fileExt; michael@0: if (!hasExtension) michael@0: { michael@0: // Test if previous extension is acceptable michael@0: nsCOMPtr oldurl(do_QueryInterface(aOriginalURIWithExtension)); michael@0: NS_ENSURE_TRUE(oldurl, NS_ERROR_FAILURE); michael@0: oldurl->GetFileExtension(fileExt); michael@0: bool useOldExt = false; michael@0: if (!fileExt.IsEmpty()) michael@0: { michael@0: mimeInfo->ExtensionExists(fileExt, &useOldExt); michael@0: } michael@0: michael@0: // can't use old extension so use primary extension michael@0: if (!useOldExt) michael@0: { michael@0: mimeInfo->GetPrimaryExtension(fileExt); michael@0: } michael@0: michael@0: if (!fileExt.IsEmpty()) michael@0: { michael@0: uint32_t newLength = newFileName.Length() + fileExt.Length() + 1; michael@0: if (newLength > kDefaultMaxFilenameLength) michael@0: { michael@0: if (fileExt.Length() > kDefaultMaxFilenameLength/2) michael@0: fileExt.Truncate(kDefaultMaxFilenameLength/2); michael@0: michael@0: uint32_t diff = kDefaultMaxFilenameLength - 1 - michael@0: fileExt.Length(); michael@0: if (newFileName.Length() > diff) michael@0: newFileName.Truncate(diff); michael@0: } michael@0: newFileName.Append("."); michael@0: newFileName.Append(fileExt); michael@0: } michael@0: michael@0: if (localFile) michael@0: { michael@0: localFile->SetLeafName(NS_ConvertUTF8toUTF16(newFileName)); michael@0: michael@0: // Resync the URI with the file after the extension has been appended michael@0: nsCOMPtr fileURL = do_QueryInterface(aURI, &rv); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: fileURL->SetFile(localFile); // this should recalculate uri michael@0: } michael@0: else michael@0: { michael@0: url->SetFileName(newFileName); michael@0: } michael@0: } michael@0: michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::MakeOutputStream( michael@0: nsIURI *aURI, nsIOutputStream **aOutputStream) michael@0: { michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr localFile; michael@0: GetLocalFileFromURI(aURI, getter_AddRefs(localFile)); michael@0: if (localFile) michael@0: rv = MakeOutputStreamFromFile(localFile, aOutputStream); michael@0: else michael@0: rv = MakeOutputStreamFromURI(aURI, aOutputStream); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::MakeOutputStreamFromFile( michael@0: nsIFile *aFile, nsIOutputStream **aOutputStream) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsCOMPtr fileOutputStream = michael@0: do_CreateInstance(NS_LOCALFILEOUTPUTSTREAM_CONTRACTID, &rv); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // XXX brade: get the right flags here! michael@0: int32_t ioFlags = -1; michael@0: if (mPersistFlags & nsIWebBrowserPersist::PERSIST_FLAGS_APPEND_TO_FILE) michael@0: ioFlags = PR_APPEND | PR_CREATE_FILE | PR_WRONLY; michael@0: rv = fileOutputStream->Init(aFile, ioFlags, -1, 0); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: *aOutputStream = NS_BufferOutputStream(fileOutputStream, michael@0: BUFFERED_OUTPUT_SIZE).take(); michael@0: michael@0: if (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE) michael@0: { michael@0: // Add to cleanup list in event of failure michael@0: CleanupData *cleanupData = new CleanupData; michael@0: if (!cleanupData) { michael@0: NS_RELEASE(*aOutputStream); michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: } michael@0: cleanupData->mFile = aFile; michael@0: cleanupData->mIsDirectory = false; michael@0: mCleanupList.AppendElement(cleanupData); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::MakeOutputStreamFromURI( michael@0: nsIURI *aURI, nsIOutputStream **aOutputStream) michael@0: { michael@0: uint32_t segsize = 8192; michael@0: uint32_t maxsize = uint32_t(-1); michael@0: nsCOMPtr storStream; michael@0: nsresult rv = NS_NewStorageStream(segsize, maxsize, getter_AddRefs(storStream)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: NS_ENSURE_SUCCESS(CallQueryInterface(storStream, aOutputStream), NS_ERROR_FAILURE); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsWebBrowserPersist::EndDownload(nsresult aResult) michael@0: { michael@0: // Store the error code in the result if it is an error michael@0: if (NS_SUCCEEDED(mPersistResult) && NS_FAILED(aResult)) michael@0: { michael@0: mPersistResult = aResult; michael@0: } michael@0: michael@0: // Do file cleanup if required michael@0: if (NS_FAILED(aResult) && (mPersistFlags & PERSIST_FLAGS_CLEANUP_ON_FAILURE)) michael@0: { michael@0: CleanupLocalFiles(); michael@0: } michael@0: michael@0: // Cleanup the channels michael@0: mCompleted = true; michael@0: Cleanup(); michael@0: } michael@0: michael@0: struct MOZ_STACK_CLASS FixRedirectData michael@0: { michael@0: nsCOMPtr mNewChannel; michael@0: nsCOMPtr mOriginalURI; michael@0: nsCOMPtr mMatchingKey; michael@0: }; michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::FixRedirectedChannelEntry(nsIChannel *aNewChannel) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNewChannel); michael@0: nsCOMPtr originalURI; michael@0: michael@0: // Enumerate through existing open channels looking for one with michael@0: // a URI matching the one specified. michael@0: michael@0: FixRedirectData data; michael@0: data.mNewChannel = aNewChannel; michael@0: data.mNewChannel->GetOriginalURI(getter_AddRefs(data.mOriginalURI)); michael@0: mOutputMap.EnumerateRead(EnumFixRedirect, &data); michael@0: michael@0: // If a match is found, remove the data entry with the old channel key michael@0: // and re-add it with the new channel key. michael@0: michael@0: if (data.mMatchingKey) michael@0: { michael@0: nsAutoPtr outputData; michael@0: mOutputMap.RemoveAndForget(data.mMatchingKey, outputData); michael@0: NS_ENSURE_TRUE(outputData, NS_ERROR_FAILURE); michael@0: michael@0: // Store data again with new channel unless told to ignore redirects michael@0: if (!(mPersistFlags & PERSIST_FLAGS_IGNORE_REDIRECTED_DATA)) michael@0: { michael@0: nsCOMPtr keyPtr = do_QueryInterface(aNewChannel); michael@0: mOutputMap.Put(keyPtr, outputData.forget()); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumFixRedirect(nsISupports *aKey, OutputData *aData, void* aClosure) michael@0: { michael@0: FixRedirectData *data = static_cast(aClosure); michael@0: michael@0: nsCOMPtr thisChannel = do_QueryInterface(aKey); michael@0: nsCOMPtr thisURI; michael@0: michael@0: thisChannel->GetOriginalURI(getter_AddRefs(thisURI)); michael@0: michael@0: // Compare this channel's URI to the one passed in. michael@0: bool matchingURI = false; michael@0: thisURI->Equals(data->mOriginalURI, &matchingURI); michael@0: if (matchingURI) michael@0: { michael@0: data->mMatchingKey = aKey; michael@0: return PL_DHASH_STOP; michael@0: } michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: void michael@0: nsWebBrowserPersist::CalcTotalProgress() michael@0: { michael@0: mTotalCurrentProgress = 0; michael@0: mTotalMaxProgress = 0; michael@0: michael@0: if (mOutputMap.Count() > 0) michael@0: { michael@0: // Total up the progress of each output stream michael@0: mOutputMap.EnumerateRead(EnumCalcProgress, this); michael@0: } michael@0: michael@0: if (mUploadList.Count() > 0) michael@0: { michael@0: // Total up the progress of each upload michael@0: mUploadList.EnumerateRead(EnumCalcUploadProgress, this); michael@0: } michael@0: michael@0: // XXX this code seems pretty bogus and pointless michael@0: if (mTotalCurrentProgress == 0 && mTotalMaxProgress == 0) michael@0: { michael@0: // No output streams so we must be complete michael@0: mTotalCurrentProgress = 10000; michael@0: mTotalMaxProgress = 10000; michael@0: } michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumCalcProgress(nsISupports *aKey, OutputData *aData, void* aClosure) michael@0: { michael@0: nsWebBrowserPersist *pthis = static_cast(aClosure); michael@0: michael@0: // only count toward total progress if destination file is local michael@0: nsCOMPtr fileURL = do_QueryInterface(aData->mFile); michael@0: if (fileURL) michael@0: { michael@0: pthis->mTotalCurrentProgress += aData->mSelfProgress; michael@0: pthis->mTotalMaxProgress += aData->mSelfProgressMax; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumCalcUploadProgress(nsISupports *aKey, UploadData *aData, void* aClosure) michael@0: { michael@0: if (aData && aClosure) michael@0: { michael@0: nsWebBrowserPersist *pthis = static_cast(aClosure); michael@0: pthis->mTotalCurrentProgress += aData->mSelfProgress; michael@0: pthis->mTotalMaxProgress += aData->mSelfProgressMax; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumCountURIsToPersist(const nsACString &aKey, URIData *aData, void* aClosure) michael@0: { michael@0: uint32_t *count = static_cast(aClosure); michael@0: if (aData->mNeedsPersisting && !aData->mSaved) michael@0: { michael@0: (*count)++; michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumPersistURIs(const nsACString &aKey, URIData *aData, void* aClosure) michael@0: { michael@0: if (!aData->mNeedsPersisting || aData->mSaved) michael@0: { michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsWebBrowserPersist *pthis = static_cast(aClosure); michael@0: nsresult rv; michael@0: michael@0: // Create a URI from the key michael@0: nsAutoCString key = nsAutoCString(aKey); michael@0: nsCOMPtr uri; michael@0: rv = NS_NewURI(getter_AddRefs(uri), michael@0: nsDependentCString(key.get(), key.Length()), michael@0: aData->mCharset.get()); michael@0: NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); michael@0: michael@0: // Make a URI to save the data to michael@0: nsCOMPtr fileAsURI; michael@0: rv = aData->mDataPath->Clone(getter_AddRefs(fileAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); michael@0: rv = pthis->AppendPathToURI(fileAsURI, aData->mFilename); michael@0: NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); michael@0: michael@0: rv = pthis->SaveURIInternal(uri, nullptr, nullptr, nullptr, nullptr, fileAsURI, true, michael@0: pthis->mIsPrivate); michael@0: // if SaveURIInternal fails, then it will have called EndDownload, michael@0: // which means that |aData| is no longer valid memory. we MUST bail. michael@0: NS_ENSURE_SUCCESS(rv, PL_DHASH_STOP); michael@0: michael@0: if (rv == NS_OK) michael@0: { michael@0: // Store the actual object because once it's persisted this michael@0: // will be fixed up with the right file extension. michael@0: michael@0: aData->mFile = fileAsURI; michael@0: aData->mSaved = true; michael@0: } michael@0: else michael@0: { michael@0: aData->mNeedsFixup = false; michael@0: } michael@0: michael@0: if (pthis->mSerializingOutput) michael@0: return PL_DHASH_STOP; michael@0: michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumCleanupOutputMap(nsISupports *aKey, OutputData *aData, void* aClosure) michael@0: { michael@0: nsCOMPtr channel = do_QueryInterface(aKey); michael@0: if (channel) michael@0: { michael@0: channel->Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: PLDHashOperator michael@0: nsWebBrowserPersist::EnumCleanupUploadList(nsISupports *aKey, UploadData *aData, void* aClosure) michael@0: { michael@0: nsCOMPtr channel = do_QueryInterface(aKey); michael@0: if (channel) michael@0: { michael@0: channel->Cancel(NS_BINDING_ABORTED); michael@0: } michael@0: return PL_DHASH_NEXT; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::FixupXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, const nsAString &aHref) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aPI); michael@0: nsresult rv = NS_OK; michael@0: michael@0: nsAutoString data; michael@0: rv = aPI->GetData(data); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: nsAutoString href; michael@0: nsContentUtils::GetPseudoAttributeValue(data, michael@0: nsGkAtoms::href, michael@0: href); michael@0: michael@0: // Construct and set a new data value for the xml-stylesheet michael@0: if (!aHref.IsEmpty() && !href.IsEmpty()) michael@0: { michael@0: nsAutoString alternate; michael@0: nsAutoString charset; michael@0: nsAutoString title; michael@0: nsAutoString type; michael@0: nsAutoString media; michael@0: michael@0: nsContentUtils::GetPseudoAttributeValue(data, michael@0: nsGkAtoms::alternate, michael@0: alternate); michael@0: nsContentUtils::GetPseudoAttributeValue(data, michael@0: nsGkAtoms::charset, michael@0: charset); michael@0: nsContentUtils::GetPseudoAttributeValue(data, michael@0: nsGkAtoms::title, michael@0: title); michael@0: nsContentUtils::GetPseudoAttributeValue(data, michael@0: nsGkAtoms::type, michael@0: type); michael@0: nsContentUtils::GetPseudoAttributeValue(data, michael@0: nsGkAtoms::media, michael@0: media); michael@0: michael@0: NS_NAMED_LITERAL_STRING(kCloseAttr, "\" "); michael@0: nsAutoString newData; michael@0: newData += NS_LITERAL_STRING("href=\"") + aHref + kCloseAttr; michael@0: if (!title.IsEmpty()) michael@0: { michael@0: newData += NS_LITERAL_STRING("title=\"") + title + kCloseAttr; michael@0: } michael@0: if (!media.IsEmpty()) michael@0: { michael@0: newData += NS_LITERAL_STRING("media=\"") + media + kCloseAttr; michael@0: } michael@0: if (!type.IsEmpty()) michael@0: { michael@0: newData += NS_LITERAL_STRING("type=\"") + type + kCloseAttr; michael@0: } michael@0: if (!charset.IsEmpty()) michael@0: { michael@0: newData += NS_LITERAL_STRING("charset=\"") + charset + kCloseAttr; michael@0: } michael@0: if (!alternate.IsEmpty()) michael@0: { michael@0: newData += NS_LITERAL_STRING("alternate=\"") + alternate + kCloseAttr; michael@0: } michael@0: newData.Truncate(newData.Length() - 1); // Remove the extra space on the end. michael@0: aPI->SetData(newData); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::GetXMLStyleSheetLink(nsIDOMProcessingInstruction *aPI, nsAString &aHref) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aPI); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsAutoString data; michael@0: rv = aPI->GetData(data); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: nsContentUtils::GetPseudoAttributeValue(data, nsGkAtoms::href, aHref); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult nsWebBrowserPersist::OnWalkDOMNode(nsIDOMNode *aNode) michael@0: { michael@0: // Fixup xml-stylesheet processing instructions michael@0: nsCOMPtr nodeAsPI = do_QueryInterface(aNode); michael@0: if (nodeAsPI) michael@0: { michael@0: nsAutoString target; michael@0: nodeAsPI->GetTarget(target); michael@0: if (target.EqualsLiteral("xml-stylesheet")) michael@0: { michael@0: nsAutoString href; michael@0: GetXMLStyleSheetLink(nodeAsPI, href); michael@0: if (!href.IsEmpty()) michael@0: { michael@0: StoreURI(NS_ConvertUTF16toUTF8(href).get()); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aNode); michael@0: if (!content) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Test the node to see if it's an image, frame, iframe, css, js michael@0: nsCOMPtr nodeAsImage = do_QueryInterface(aNode); michael@0: if (nodeAsImage) michael@0: { michael@0: StoreURIAttribute(aNode, "src"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content->IsSVG(nsGkAtoms::img)) michael@0: { michael@0: StoreURIAttributeNS(aNode, "http://www.w3.org/1999/xlink", "href"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsMedia = do_QueryInterface(aNode); michael@0: if (nodeAsMedia) michael@0: { michael@0: StoreURIAttribute(aNode, "src"); michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr nodeAsSource = do_QueryInterface(aNode); michael@0: if (nodeAsSource) michael@0: { michael@0: StoreURIAttribute(aNode, "src"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::body)) { michael@0: StoreURIAttribute(aNode, "background"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::table)) { michael@0: StoreURIAttribute(aNode, "background"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::tr)) { michael@0: StoreURIAttribute(aNode, "background"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::td) || content->IsHTML(nsGkAtoms::th)) { michael@0: StoreURIAttribute(aNode, "background"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsScript = do_QueryInterface(aNode); michael@0: if (nodeAsScript) michael@0: { michael@0: StoreURIAttribute(aNode, "src"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (content->IsSVG(nsGkAtoms::script)) michael@0: { michael@0: StoreURIAttributeNS(aNode, "http://www.w3.org/1999/xlink", "href"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsEmbed = do_QueryInterface(aNode); michael@0: if (nodeAsEmbed) michael@0: { michael@0: StoreURIAttribute(aNode, "src"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsObject = do_QueryInterface(aNode); michael@0: if (nodeAsObject) michael@0: { michael@0: StoreURIAttribute(aNode, "data"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsApplet = do_QueryInterface(aNode); michael@0: if (nodeAsApplet) michael@0: { michael@0: // For an applet, relative URIs are resolved relative to the michael@0: // codebase (which is resolved relative to the base URI). michael@0: nsCOMPtr oldBase = mCurrentBaseURI; michael@0: nsAutoString codebase; michael@0: nodeAsApplet->GetCodeBase(codebase); michael@0: if (!codebase.IsEmpty()) { michael@0: nsCOMPtr baseURI; michael@0: NS_NewURI(getter_AddRefs(baseURI), codebase, michael@0: mCurrentCharset.get(), mCurrentBaseURI); michael@0: if (baseURI) { michael@0: mCurrentBaseURI = baseURI; michael@0: } michael@0: } michael@0: michael@0: URIData *archiveURIData = nullptr; michael@0: StoreURIAttribute(aNode, "archive", true, &archiveURIData); michael@0: // We only store 'code' locally if there is no 'archive', michael@0: // otherwise we assume the archive file(s) contains it (bug 430283). michael@0: if (!archiveURIData) michael@0: StoreURIAttribute(aNode, "code"); michael@0: michael@0: // restore the base URI we really want to have michael@0: mCurrentBaseURI = oldBase; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsLink = do_QueryInterface(aNode); michael@0: if (nodeAsLink) michael@0: { michael@0: // Test if the link has a rel value indicating it to be a stylesheet michael@0: nsAutoString linkRel; michael@0: if (NS_SUCCEEDED(nodeAsLink->GetRel(linkRel)) && !linkRel.IsEmpty()) michael@0: { michael@0: nsReadingIterator start; michael@0: nsReadingIterator end; michael@0: nsReadingIterator current; michael@0: michael@0: linkRel.BeginReading(start); michael@0: linkRel.EndReading(end); michael@0: michael@0: // Walk through space delimited string looking for "stylesheet" michael@0: for (current = start; current != end; ++current) michael@0: { michael@0: // Ignore whitespace michael@0: if (nsCRT::IsAsciiSpace(*current)) michael@0: continue; michael@0: michael@0: // Grab the next space delimited word michael@0: nsReadingIterator startWord = current; michael@0: do { michael@0: ++current; michael@0: } while (current != end && !nsCRT::IsAsciiSpace(*current)); michael@0: michael@0: // Store the link for fix up if it says "stylesheet" michael@0: if (Substring(startWord, current) michael@0: .LowerCaseEqualsLiteral("stylesheet")) michael@0: { michael@0: StoreURIAttribute(aNode, "href"); michael@0: return NS_OK; michael@0: } michael@0: if (current == end) michael@0: break; michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsFrame = do_QueryInterface(aNode); michael@0: if (nodeAsFrame) michael@0: { michael@0: URIData *data = nullptr; michael@0: StoreURIAttribute(aNode, "src", false, &data); michael@0: if (data) michael@0: { michael@0: data->mIsSubFrame = true; michael@0: // Save the frame content michael@0: nsCOMPtr content; michael@0: nodeAsFrame->GetContentDocument(getter_AddRefs(content)); michael@0: if (content) michael@0: { michael@0: SaveSubframeContent(content, data); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsIFrame = do_QueryInterface(aNode); michael@0: if (nodeAsIFrame && !(mPersistFlags & PERSIST_FLAGS_IGNORE_IFRAMES)) michael@0: { michael@0: URIData *data = nullptr; michael@0: StoreURIAttribute(aNode, "src", false, &data); michael@0: if (data) michael@0: { michael@0: data->mIsSubFrame = true; michael@0: // Save the frame content michael@0: nsCOMPtr content; michael@0: nodeAsIFrame->GetContentDocument(getter_AddRefs(content)); michael@0: if (content) michael@0: { michael@0: SaveSubframeContent(content, data); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsInput = do_QueryInterface(aNode); michael@0: if (nodeAsInput) michael@0: { michael@0: StoreURIAttribute(aNode, "src"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::GetNodeToFixup(nsIDOMNode *aNodeIn, nsIDOMNode **aNodeOut) michael@0: { michael@0: if (!(mPersistFlags & PERSIST_FLAGS_FIXUP_ORIGINAL_DOM)) michael@0: { michael@0: nsresult rv = aNodeIn->CloneNode(false, 1, aNodeOut); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: NS_ADDREF(*aNodeOut = aNodeIn); michael@0: } michael@0: nsCOMPtr element(do_QueryInterface(*aNodeOut)); michael@0: if (element) { michael@0: // Make sure this is not XHTML michael@0: nsAutoString namespaceURI; michael@0: element->GetNamespaceURI(namespaceURI); michael@0: if (namespaceURI.IsEmpty()) { michael@0: // This is a tag-soup node. It may have a _base_href attribute michael@0: // stuck on it by the parser, but since we're fixing up all URIs michael@0: // relative to the overall document base that will screw us up. michael@0: // Just remove the _base_href. michael@0: element->RemoveAttribute(NS_LITERAL_STRING("_base_href")); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::CloneNodeWithFixedUpAttributes( michael@0: nsIDOMNode *aNodeIn, bool *aSerializeCloneKids, nsIDOMNode **aNodeOut) michael@0: { michael@0: nsresult rv; michael@0: *aNodeOut = nullptr; michael@0: *aSerializeCloneKids = false; michael@0: michael@0: // Fixup xml-stylesheet processing instructions michael@0: nsCOMPtr nodeAsPI = do_QueryInterface(aNodeIn); michael@0: if (nodeAsPI) michael@0: { michael@0: nsAutoString target; michael@0: nodeAsPI->GetTarget(target); michael@0: if (target.EqualsLiteral("xml-stylesheet")) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: nsCOMPtr outNode = do_QueryInterface(*aNodeOut); michael@0: nsAutoString href; michael@0: GetXMLStyleSheetLink(nodeAsPI, href); michael@0: if (!href.IsEmpty()) michael@0: { michael@0: FixupURI(href); michael@0: FixupXMLStyleSheetLink(outNode, href); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // BASE elements are replaced by a comment so relative links are not hosed. michael@0: michael@0: if (!(mPersistFlags & PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS)) michael@0: { michael@0: nsCOMPtr nodeAsBase = do_QueryInterface(aNodeIn); michael@0: if (nodeAsBase) michael@0: { michael@0: nsCOMPtr ownerDocument; michael@0: HTMLSharedElement* base = static_cast(nodeAsBase.get()); michael@0: base->GetOwnerDocument(getter_AddRefs(ownerDocument)); michael@0: if (ownerDocument) michael@0: { michael@0: nsAutoString href; michael@0: base->GetHref(href); // Doesn't matter if this fails michael@0: nsCOMPtr comment; michael@0: nsAutoString commentText; commentText.AssignLiteral(" base "); michael@0: if (!href.IsEmpty()) michael@0: { michael@0: commentText += NS_LITERAL_STRING("href=\"") + href + NS_LITERAL_STRING("\" "); michael@0: } michael@0: rv = ownerDocument->CreateComment(commentText, getter_AddRefs(comment)); michael@0: if (comment) michael@0: { michael@0: return CallQueryInterface(comment, aNodeOut); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr content = do_QueryInterface(aNodeIn); michael@0: if (!content) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Fix up href and file links in the elements michael@0: michael@0: nsCOMPtr nodeAsAnchor = do_QueryInterface(aNodeIn); michael@0: if (nodeAsAnchor) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupAnchor(*aNodeOut); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsArea = do_QueryInterface(aNodeIn); michael@0: if (nodeAsArea) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupAnchor(*aNodeOut); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::body)) { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "background"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::table)) { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "background"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::tr)) { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "background"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (content->IsHTML(nsGkAtoms::td) || content->IsHTML(nsGkAtoms::th)) { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "background"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsImage = do_QueryInterface(aNodeIn); michael@0: if (nodeAsImage) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: // Disable image loads michael@0: nsCOMPtr imgCon = michael@0: do_QueryInterface(*aNodeOut); michael@0: if (imgCon) michael@0: imgCon->SetLoadingEnabled(false); michael@0: michael@0: FixupAnchor(*aNodeOut); michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsMedia = do_QueryInterface(aNodeIn); michael@0: if (nodeAsMedia) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsSource = do_QueryInterface(aNodeIn); michael@0: if (nodeAsSource) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: if (content->IsSVG(nsGkAtoms::img)) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: // Disable image loads michael@0: nsCOMPtr imgCon = michael@0: do_QueryInterface(*aNodeOut); michael@0: if (imgCon) michael@0: imgCon->SetLoadingEnabled(false); michael@0: michael@0: // FixupAnchor(*aNodeOut); // XXXjwatt: is this line needed? michael@0: FixupNodeAttributeNS(*aNodeOut, "http://www.w3.org/1999/xlink", "href"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsScript = do_QueryInterface(aNodeIn); michael@0: if (nodeAsScript) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: if (content->IsSVG(nsGkAtoms::script)) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttributeNS(*aNodeOut, "http://www.w3.org/1999/xlink", "href"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsEmbed = do_QueryInterface(aNodeIn); michael@0: if (nodeAsEmbed) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsObject = do_QueryInterface(aNodeIn); michael@0: if (nodeAsObject) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "data"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsApplet = do_QueryInterface(aNodeIn); michael@0: if (nodeAsApplet) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: nsCOMPtr newApplet = michael@0: do_QueryInterface(*aNodeOut); michael@0: // For an applet, relative URIs are resolved relative to the michael@0: // codebase (which is resolved relative to the base URI). michael@0: nsCOMPtr oldBase = mCurrentBaseURI; michael@0: nsAutoString codebase; michael@0: nodeAsApplet->GetCodeBase(codebase); michael@0: if (!codebase.IsEmpty()) { michael@0: nsCOMPtr baseURI; michael@0: NS_NewURI(getter_AddRefs(baseURI), codebase, michael@0: mCurrentCharset.get(), mCurrentBaseURI); michael@0: if (baseURI) { michael@0: mCurrentBaseURI = baseURI; michael@0: } michael@0: } michael@0: // Unset the codebase too, since we'll correctly relativize the michael@0: // code and archive paths. michael@0: static_cast(newApplet.get())-> michael@0: RemoveAttribute(NS_LITERAL_STRING("codebase")); michael@0: FixupNodeAttribute(*aNodeOut, "code"); michael@0: FixupNodeAttribute(*aNodeOut, "archive"); michael@0: // restore the base URI we really want to have michael@0: mCurrentBaseURI = oldBase; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsLink = do_QueryInterface(aNodeIn); michael@0: if (nodeAsLink) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: // First see if the link represents linked content michael@0: rv = FixupNodeAttribute(*aNodeOut, "href"); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: // Perhaps this link is actually an anchor to related content michael@0: FixupAnchor(*aNodeOut); michael@0: } michael@0: // TODO if "type" attribute == "text/css" michael@0: // fixup stylesheet michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsFrame = do_QueryInterface(aNodeIn); michael@0: if (nodeAsFrame) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsIFrame = do_QueryInterface(aNodeIn); michael@0: if (nodeAsIFrame) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsInput = do_QueryInterface(aNodeIn); michael@0: if (nodeAsInput) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: // Disable image loads michael@0: nsCOMPtr imgCon = michael@0: do_QueryInterface(*aNodeOut); michael@0: if (imgCon) michael@0: imgCon->SetLoadingEnabled(false); michael@0: michael@0: FixupNodeAttribute(*aNodeOut, "src"); michael@0: michael@0: nsAutoString valueStr; michael@0: NS_NAMED_LITERAL_STRING(valueAttr, "value"); michael@0: // Update element node attributes with user-entered form state michael@0: nsCOMPtr content = do_QueryInterface(*aNodeOut); michael@0: nsRefPtr outElt = michael@0: HTMLInputElement::FromContentOrNull(content); michael@0: nsCOMPtr formControl = do_QueryInterface(*aNodeOut); michael@0: switch (formControl->GetType()) { michael@0: case NS_FORM_INPUT_EMAIL: michael@0: case NS_FORM_INPUT_SEARCH: michael@0: case NS_FORM_INPUT_TEXT: michael@0: case NS_FORM_INPUT_TEL: michael@0: case NS_FORM_INPUT_URL: michael@0: case NS_FORM_INPUT_NUMBER: michael@0: case NS_FORM_INPUT_RANGE: michael@0: case NS_FORM_INPUT_DATE: michael@0: case NS_FORM_INPUT_TIME: michael@0: case NS_FORM_INPUT_COLOR: michael@0: nodeAsInput->GetValue(valueStr); michael@0: // Avoid superfluous value="" serialization michael@0: if (valueStr.IsEmpty()) michael@0: outElt->RemoveAttribute(valueAttr); michael@0: else michael@0: outElt->SetAttribute(valueAttr, valueStr); michael@0: break; michael@0: case NS_FORM_INPUT_CHECKBOX: michael@0: case NS_FORM_INPUT_RADIO: michael@0: bool checked; michael@0: nodeAsInput->GetChecked(&checked); michael@0: outElt->SetDefaultChecked(checked); michael@0: break; michael@0: default: michael@0: break; michael@0: } michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsTextArea = do_QueryInterface(aNodeIn); michael@0: if (nodeAsTextArea) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: // Tell the document encoder to serialize the text child we create below michael@0: *aSerializeCloneKids = true; michael@0: michael@0: nsAutoString valueStr; michael@0: nodeAsTextArea->GetValue(valueStr); michael@0: michael@0: (*aNodeOut)->SetTextContent(valueStr); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsCOMPtr nodeAsOption = do_QueryInterface(aNodeIn); michael@0: if (nodeAsOption) michael@0: { michael@0: rv = GetNodeToFixup(aNodeIn, aNodeOut); michael@0: if (NS_SUCCEEDED(rv) && *aNodeOut) michael@0: { michael@0: nsCOMPtr outElt = do_QueryInterface(*aNodeOut); michael@0: bool selected; michael@0: nodeAsOption->GetSelected(&selected); michael@0: outElt->SetDefaultSelected(selected); michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::StoreURI( michael@0: const char *aURI, bool aNeedsPersisting, URIData **aData) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), michael@0: nsDependentCString(aURI), michael@0: mCurrentCharset.get(), michael@0: mCurrentBaseURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return StoreURI(uri, aNeedsPersisting, aData); michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::StoreURI( michael@0: nsIURI *aURI, bool aNeedsPersisting, URIData **aData) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: if (aData) michael@0: { michael@0: *aData = nullptr; michael@0: } michael@0: michael@0: // Test if this URI should be persisted. By default michael@0: // we should assume the URI is persistable. michael@0: bool doNotPersistURI; michael@0: nsresult rv = NS_URIChainHasFlags(aURI, michael@0: nsIProtocolHandler::URI_NON_PERSISTABLE, michael@0: &doNotPersistURI); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: doNotPersistURI = false; michael@0: } michael@0: michael@0: if (doNotPersistURI) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: URIData *data = nullptr; michael@0: MakeAndStoreLocalFilenameInURIMap(aURI, aNeedsPersisting, &data); michael@0: if (aData) michael@0: { michael@0: *aData = data; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::StoreURIAttributeNS( michael@0: nsIDOMNode *aNode, const char *aNamespaceURI, const char *aAttribute, michael@0: bool aNeedsPersisting, URIData **aData) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNode); michael@0: NS_ENSURE_ARG_POINTER(aNamespaceURI); michael@0: NS_ENSURE_ARG_POINTER(aAttribute); michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: MOZ_ASSERT(element); michael@0: michael@0: // Find the named URI attribute on the (element) node and store michael@0: // a reference to the URI that maps onto a local file name michael@0: michael@0: nsCOMPtr attrMap; michael@0: nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); michael@0: NS_ConvertASCIItoUTF16 attribute(aAttribute); michael@0: nsCOMPtr attr; michael@0: rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); michael@0: if (attr) michael@0: { michael@0: nsAutoString oldValue; michael@0: attr->GetValue(oldValue); michael@0: if (!oldValue.IsEmpty()) michael@0: { michael@0: NS_ConvertUTF16toUTF8 oldCValue(oldValue); michael@0: return StoreURI(oldCValue.get(), aNeedsPersisting, aData); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::FixupURI(nsAString &aURI) michael@0: { michael@0: // get the current location of the file (absolutized) michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), aURI, michael@0: mCurrentCharset.get(), mCurrentBaseURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsAutoCString spec; michael@0: rv = uri->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Search for the URI in the map and replace it with the local file michael@0: if (!mURIMap.Contains(spec)) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: URIData *data = mURIMap.Get(spec); michael@0: if (!data->mNeedsFixup) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: nsCOMPtr fileAsURI; michael@0: if (data->mFile) michael@0: { michael@0: rv = data->mFile->Clone(getter_AddRefs(fileAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: else michael@0: { michael@0: rv = data->mDataPath->Clone(getter_AddRefs(fileAsURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = AppendPathToURI(fileAsURI, data->mFilename); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: nsAutoString newValue; michael@0: michael@0: // remove username/password if present michael@0: fileAsURI->SetUserPass(EmptyCString()); michael@0: michael@0: // reset node attribute michael@0: // Use relative or absolute links michael@0: if (data->mDataPathIsRelative) michael@0: { michael@0: nsCOMPtr url(do_QueryInterface(fileAsURI)); michael@0: if (!url) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsAutoCString filename; michael@0: url->GetFileName(filename); michael@0: michael@0: nsAutoCString rawPathURL(data->mRelativePathToData); michael@0: rawPathURL.Append(filename); michael@0: michael@0: nsAutoCString buf; michael@0: AppendUTF8toUTF16(NS_EscapeURL(rawPathURL, esc_FilePath, buf), michael@0: newValue); michael@0: } michael@0: else michael@0: { michael@0: nsAutoCString fileurl; michael@0: fileAsURI->GetSpec(fileurl); michael@0: AppendUTF8toUTF16(fileurl, newValue); michael@0: } michael@0: if (data->mIsSubFrame) michael@0: { michael@0: newValue.Append(data->mSubFrameExt); michael@0: } michael@0: michael@0: aURI = newValue; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::FixupNodeAttributeNS(nsIDOMNode *aNode, michael@0: const char *aNamespaceURI, michael@0: const char *aAttribute) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNode); michael@0: NS_ENSURE_ARG_POINTER(aNamespaceURI); michael@0: NS_ENSURE_ARG_POINTER(aAttribute); michael@0: michael@0: // Find the named URI attribute on the (element) node and change it to reference michael@0: // a local file. michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: MOZ_ASSERT(element); michael@0: michael@0: nsCOMPtr attrMap; michael@0: nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: NS_ConvertASCIItoUTF16 attribute(aAttribute); michael@0: NS_ConvertASCIItoUTF16 namespaceURI(aNamespaceURI); michael@0: nsCOMPtr attr; michael@0: rv = attrMap->GetNamedItemNS(namespaceURI, attribute, getter_AddRefs(attr)); michael@0: if (attr) { michael@0: nsString uri; michael@0: attr->GetValue(uri); michael@0: rv = FixupURI(uri); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: attr->SetValue(uri); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::FixupAnchor(nsIDOMNode *aNode) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNode); michael@0: michael@0: nsCOMPtr element = do_QueryInterface(aNode); michael@0: MOZ_ASSERT(element); michael@0: michael@0: nsCOMPtr attrMap; michael@0: nsresult rv = element->GetAttributes(getter_AddRefs(attrMap)); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: if (mPersistFlags & PERSIST_FLAGS_DONT_FIXUP_LINKS) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Make all anchor links absolute so they point off onto the Internet michael@0: nsString attribute(NS_LITERAL_STRING("href")); michael@0: nsCOMPtr attr; michael@0: rv = attrMap->GetNamedItem(attribute, getter_AddRefs(attr)); michael@0: if (attr) michael@0: { michael@0: nsString oldValue; michael@0: attr->GetValue(oldValue); michael@0: NS_ConvertUTF16toUTF8 oldCValue(oldValue); michael@0: michael@0: // Skip empty values and self-referencing bookmarks michael@0: if (oldCValue.IsEmpty() || oldCValue.CharAt(0) == '#') michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: // if saving file to same location, we don't need to do any fixup michael@0: bool isEqual = false; michael@0: if (NS_SUCCEEDED(mCurrentBaseURI->Equals(mTargetBaseURI, &isEqual)) michael@0: && isEqual) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr relativeURI; michael@0: relativeURI = (mPersistFlags & PERSIST_FLAGS_FIXUP_LINKS_TO_DESTINATION) michael@0: ? mTargetBaseURI : mCurrentBaseURI; michael@0: // Make a new URI to replace the current one michael@0: nsCOMPtr newURI; michael@0: rv = NS_NewURI(getter_AddRefs(newURI), oldCValue, michael@0: mCurrentCharset.get(), relativeURI); michael@0: if (NS_SUCCEEDED(rv) && newURI) michael@0: { michael@0: newURI->SetUserPass(EmptyCString()); michael@0: nsAutoCString uriSpec; michael@0: newURI->GetSpec(uriSpec); michael@0: attr->SetValue(NS_ConvertUTF8toUTF16(uriSpec)); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::StoreAndFixupStyleSheet(nsIStyleSheet *aStyleSheet) michael@0: { michael@0: // TODO go through the style sheet fixing up all links michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsWebBrowserPersist::DocumentEncoderExists(const char16_t *aContentType) michael@0: { michael@0: // Check if there is an encoder for the desired content type. michael@0: nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); michael@0: AppendUTF16toUTF8(aContentType, contractID); michael@0: michael@0: nsCOMPtr registrar; michael@0: NS_GetComponentRegistrar(getter_AddRefs(registrar)); michael@0: if (registrar) michael@0: { michael@0: bool result; michael@0: nsresult rv = registrar->IsContractIDRegistered(contractID.get(), michael@0: &result); michael@0: if (NS_SUCCEEDED(rv) && result) michael@0: { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::SaveSubframeContent( michael@0: nsIDOMDocument *aFrameContent, URIData *aData) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aData); michael@0: michael@0: // Extract the content type for the frame's contents. michael@0: nsCOMPtr frameDoc(do_QueryInterface(aFrameContent)); michael@0: NS_ENSURE_STATE(frameDoc); michael@0: michael@0: nsAutoString contentType; michael@0: nsresult rv = frameDoc->GetContentType(contentType); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsXPIDLString ext; michael@0: GetExtensionForContentType(contentType.get(), getter_Copies(ext)); michael@0: michael@0: // We must always have an extension so we will try to re-assign michael@0: // the original extension if GetExtensionForContentType fails. michael@0: if (ext.IsEmpty()) michael@0: { michael@0: nsCOMPtr url(do_QueryInterface(frameDoc->GetDocumentURI(), michael@0: &rv)); michael@0: nsAutoCString extension; michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: url->GetFileExtension(extension); michael@0: } michael@0: else michael@0: { michael@0: extension.AssignLiteral("htm"); michael@0: } michael@0: aData->mSubFrameExt.Assign(char16_t('.')); michael@0: AppendUTF8toUTF16(extension, aData->mSubFrameExt); michael@0: } michael@0: else michael@0: { michael@0: aData->mSubFrameExt.Assign(char16_t('.')); michael@0: aData->mSubFrameExt.Append(ext); michael@0: } michael@0: michael@0: nsString filenameWithExt = aData->mFilename; michael@0: filenameWithExt.Append(aData->mSubFrameExt); michael@0: michael@0: // Work out the path for the subframe michael@0: nsCOMPtr frameURI; michael@0: rv = mCurrentDataPath->Clone(getter_AddRefs(frameURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = AppendPathToURI(frameURI, filenameWithExt); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Work out the path for the subframe data michael@0: nsCOMPtr frameDataURI; michael@0: rv = mCurrentDataPath->Clone(getter_AddRefs(frameDataURI)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: nsAutoString newFrameDataPath(aData->mFilename); michael@0: michael@0: // Append _data michael@0: newFrameDataPath.AppendLiteral("_data"); michael@0: rv = AppendPathToURI(frameDataURI, newFrameDataPath); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Make frame document & data path conformant and unique michael@0: rv = CalculateUniqueFilename(frameURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: rv = CalculateUniqueFilename(frameDataURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mCurrentThingsToPersist++; michael@0: michael@0: // We shouldn't use SaveDocumentInternal for the contents michael@0: // of frames that are not documents, e.g. images. michael@0: if (DocumentEncoderExists(contentType.get())) michael@0: { michael@0: rv = SaveDocumentInternal(aFrameContent, frameURI, frameDataURI); michael@0: } michael@0: else michael@0: { michael@0: rv = StoreURI(frameDoc->GetDocumentURI()); michael@0: } michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: // Store the updated uri to the frame michael@0: aData->mFile = frameURI; michael@0: aData->mSubFrameExt.Truncate(); // we already put this in frameURI michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::CreateChannelFromURI(nsIURI *aURI, nsIChannel **aChannel) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: *aChannel = nullptr; michael@0: michael@0: nsCOMPtr ioserv; michael@0: ioserv = do_GetIOService(&rv); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: rv = ioserv->NewChannelFromURI(aURI, aChannel); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: NS_ENSURE_ARG_POINTER(*aChannel); michael@0: michael@0: rv = (*aChannel)->SetNotificationCallbacks(static_cast(this)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsWebBrowserPersist::SaveDocumentWithFixup( michael@0: nsIDOMDocument *aDocument, nsIDocumentEncoderNodeFixup *aNodeFixup, michael@0: nsIURI *aFile, bool aReplaceExisting, const nsACString &aFormatType, michael@0: const nsCString &aSaveCharset, uint32_t aFlags) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aFile); michael@0: michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr localFile; michael@0: GetLocalFileFromURI(aFile, getter_AddRefs(localFile)); michael@0: if (localFile) michael@0: { michael@0: // if we're not replacing an existing file but the file michael@0: // exists, something is wrong michael@0: bool fileExists = false; michael@0: rv = localFile->Exists(&fileExists); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: if (!aReplaceExisting && fileExists) michael@0: return NS_ERROR_FAILURE; // where are the file I/O errors? michael@0: } michael@0: michael@0: nsCOMPtr outputStream; michael@0: rv = MakeOutputStream(aFile, getter_AddRefs(outputStream)); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: SendErrorStatusChange(false, rv, nullptr, aFile); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: NS_ENSURE_TRUE(outputStream, NS_ERROR_FAILURE); michael@0: michael@0: // Get a document encoder instance michael@0: nsAutoCString contractID(NS_DOC_ENCODER_CONTRACTID_BASE); michael@0: contractID.Append(aFormatType); michael@0: michael@0: nsCOMPtr encoder = do_CreateInstance(contractID.get(), &rv); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: NS_ConvertASCIItoUTF16 newContentType(aFormatType); michael@0: rv = encoder->Init(aDocument, newContentType, aFlags); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: mTargetBaseURI = aFile; michael@0: michael@0: // Set the node fixup callback michael@0: encoder->SetNodeFixup(aNodeFixup); michael@0: michael@0: if (mWrapColumn && (aFlags & ENCODE_FLAGS_WRAP)) michael@0: encoder->SetWrapColumn(mWrapColumn); michael@0: michael@0: nsAutoCString charsetStr(aSaveCharset); michael@0: if (charsetStr.IsEmpty()) michael@0: { michael@0: nsCOMPtr doc = do_QueryInterface(aDocument); michael@0: NS_ASSERTION(doc, "Need a document"); michael@0: charsetStr = doc->GetDocumentCharacterSet(); michael@0: } michael@0: michael@0: rv = encoder->SetCharset(charsetStr); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: rv = encoder->EncodeToStream(outputStream); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: if (!localFile) michael@0: { michael@0: nsCOMPtr storStream(do_QueryInterface(outputStream)); michael@0: if (storStream) michael@0: { michael@0: outputStream->Close(); michael@0: rv = StartUpload(storStream, aFile, aFormatType); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: michael@0: // we store the current location as the key (absolutized version of domnode's attribute's value) michael@0: nsresult michael@0: nsWebBrowserPersist::MakeAndStoreLocalFilenameInURIMap( michael@0: nsIURI *aURI, bool aNeedsPersisting, URIData **aData) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aURI); michael@0: michael@0: nsAutoCString spec; michael@0: nsresult rv = aURI->GetSpec(spec); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // Create a sensibly named filename for the URI and store in the URI map michael@0: URIData *data; michael@0: if (mURIMap.Contains(spec)) michael@0: { michael@0: data = mURIMap.Get(spec); michael@0: if (aNeedsPersisting) michael@0: { michael@0: data->mNeedsPersisting = true; michael@0: } michael@0: if (aData) michael@0: { michael@0: *aData = data; michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Create a unique file name for the uri michael@0: nsString filename; michael@0: rv = MakeFilenameFromURI(aURI, filename); michael@0: NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); michael@0: michael@0: // Store the file name michael@0: data = new URIData; michael@0: NS_ENSURE_TRUE(data, NS_ERROR_OUT_OF_MEMORY); michael@0: michael@0: data->mNeedsPersisting = aNeedsPersisting; michael@0: data->mNeedsFixup = true; michael@0: data->mFilename = filename; michael@0: data->mSaved = false; michael@0: data->mIsSubFrame = false; michael@0: data->mDataPath = mCurrentDataPath; michael@0: data->mDataPathIsRelative = mCurrentDataPathIsRelative; michael@0: data->mRelativePathToData = mCurrentRelativePathToData; michael@0: data->mCharset = mCurrentCharset; michael@0: michael@0: if (aNeedsPersisting) michael@0: mCurrentThingsToPersist++; michael@0: michael@0: mURIMap.Put(spec, data); michael@0: if (aData) michael@0: { michael@0: *aData = data; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Ordered so that typical documents work fastest. michael@0: // strlen("blockquote")==10 michael@0: static const char kSpecialXHTMLTags[][11] = { michael@0: "body", michael@0: "head", michael@0: "img", michael@0: "script", michael@0: "a", michael@0: "area", michael@0: "link", michael@0: "input", michael@0: "frame", michael@0: "iframe", michael@0: "object", michael@0: "applet", michael@0: "form", michael@0: "blockquote", michael@0: "q", michael@0: "del", michael@0: "ins" michael@0: }; michael@0: michael@0: static bool IsSpecialXHTMLTag(nsIDOMNode *aNode) michael@0: { michael@0: nsAutoString tmp; michael@0: aNode->GetNamespaceURI(tmp); michael@0: if (!tmp.EqualsLiteral("http://www.w3.org/1999/xhtml")) michael@0: return false; michael@0: michael@0: aNode->GetLocalName(tmp); michael@0: for (uint32_t i = 0; i < ArrayLength(kSpecialXHTMLTags); i++) { michael@0: if (tmp.EqualsASCII(kSpecialXHTMLTags[i])) michael@0: { michael@0: // XXX This element MAY have URI attributes, but michael@0: // we are not actually checking if they are present. michael@0: // That would slow us down further, and I am not so sure michael@0: // how important that would be. michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool HasSpecialXHTMLTags(nsIDOMNode *aParent) michael@0: { michael@0: if (IsSpecialXHTMLTag(aParent)) michael@0: return true; michael@0: michael@0: nsCOMPtr list; michael@0: aParent->GetChildNodes(getter_AddRefs(list)); michael@0: if (list) michael@0: { michael@0: uint32_t count; michael@0: list->GetLength(&count); michael@0: uint32_t i; michael@0: for (i = 0; i < count; i++) { michael@0: nsCOMPtr node; michael@0: list->Item(i, getter_AddRefs(node)); michael@0: if (!node) michael@0: break; michael@0: uint16_t nodeType; michael@0: node->GetNodeType(&nodeType); michael@0: if (nodeType == nsIDOMNode::ELEMENT_NODE) { michael@0: return HasSpecialXHTMLTags(node); michael@0: } michael@0: } michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: static bool NeedXHTMLBaseTag(nsIDOMDocument *aDocument) michael@0: { michael@0: nsCOMPtr docElement; michael@0: aDocument->GetDocumentElement(getter_AddRefs(docElement)); michael@0: michael@0: nsCOMPtr node(do_QueryInterface(docElement)); michael@0: if (node) michael@0: { michael@0: return HasSpecialXHTMLTags(node); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: // Set document base. This could create an invalid XML document (still well-formed). michael@0: nsresult michael@0: nsWebBrowserPersist::SetDocumentBase( michael@0: nsIDOMDocument *aDocument, nsIURI *aBaseURI) michael@0: { michael@0: if (mPersistFlags & PERSIST_FLAGS_NO_BASE_TAG_MODIFICATIONS) michael@0: { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ENSURE_ARG_POINTER(aBaseURI); michael@0: michael@0: nsCOMPtr xmlDoc; michael@0: nsCOMPtr htmlDoc = do_QueryInterface(aDocument); michael@0: if (!htmlDoc) michael@0: { michael@0: xmlDoc = do_QueryInterface(aDocument); michael@0: if (!xmlDoc) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: NS_NAMED_LITERAL_STRING(kXHTMLNS, "http://www.w3.org/1999/xhtml"); michael@0: NS_NAMED_LITERAL_STRING(kHead, "head"); michael@0: michael@0: // Find the head element michael@0: nsCOMPtr headElement; michael@0: nsCOMPtr headList; michael@0: if (xmlDoc) michael@0: { michael@0: // First see if there is XHTML content that needs base michael@0: // tags. michael@0: if (!NeedXHTMLBaseTag(aDocument)) michael@0: return NS_OK; michael@0: michael@0: aDocument->GetElementsByTagNameNS( michael@0: kXHTMLNS, michael@0: kHead, getter_AddRefs(headList)); michael@0: } michael@0: else michael@0: { michael@0: aDocument->GetElementsByTagName( michael@0: kHead, getter_AddRefs(headList)); michael@0: } michael@0: if (headList) michael@0: { michael@0: nsCOMPtr headNode; michael@0: headList->Item(0, getter_AddRefs(headNode)); michael@0: headElement = do_QueryInterface(headNode); michael@0: } michael@0: if (!headElement) michael@0: { michael@0: // Create head and insert as first element michael@0: nsCOMPtr firstChildNode; michael@0: nsCOMPtr newNode; michael@0: if (xmlDoc) michael@0: { michael@0: aDocument->CreateElementNS( michael@0: kXHTMLNS, michael@0: kHead, getter_AddRefs(headElement)); michael@0: } michael@0: else michael@0: { michael@0: aDocument->CreateElement( michael@0: kHead, getter_AddRefs(headElement)); michael@0: } michael@0: nsCOMPtr documentElement; michael@0: aDocument->GetDocumentElement(getter_AddRefs(documentElement)); michael@0: if (documentElement) michael@0: { michael@0: documentElement->GetFirstChild(getter_AddRefs(firstChildNode)); michael@0: documentElement->InsertBefore(headElement, firstChildNode, getter_AddRefs(newNode)); michael@0: } michael@0: } michael@0: if (!headElement) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: // Find or create the BASE element michael@0: NS_NAMED_LITERAL_STRING(kBase, "base"); michael@0: nsCOMPtr baseElement; michael@0: nsCOMPtr baseList; michael@0: if (xmlDoc) michael@0: { michael@0: headElement->GetElementsByTagNameNS( michael@0: kXHTMLNS, michael@0: kBase, getter_AddRefs(baseList)); michael@0: } michael@0: else michael@0: { michael@0: headElement->GetElementsByTagName( michael@0: kBase, getter_AddRefs(baseList)); michael@0: } michael@0: if (baseList) michael@0: { michael@0: nsCOMPtr baseNode; michael@0: baseList->Item(0, getter_AddRefs(baseNode)); michael@0: baseElement = do_QueryInterface(baseNode); michael@0: } michael@0: michael@0: // Add the BASE element michael@0: if (!baseElement) michael@0: { michael@0: nsCOMPtr newNode; michael@0: if (xmlDoc) michael@0: { michael@0: aDocument->CreateElementNS( michael@0: kXHTMLNS, michael@0: kBase, getter_AddRefs(baseElement)); michael@0: } michael@0: else michael@0: { michael@0: aDocument->CreateElement( michael@0: kBase, getter_AddRefs(baseElement)); michael@0: } michael@0: headElement->AppendChild(baseElement, getter_AddRefs(newNode)); michael@0: } michael@0: if (!baseElement) michael@0: { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: nsAutoCString uriSpec; michael@0: aBaseURI->GetSpec(uriSpec); michael@0: NS_ConvertUTF8toUTF16 href(uriSpec); michael@0: baseElement->SetAttribute(NS_LITERAL_STRING("href"), href); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Decide if we need to apply conversion to the passed channel. michael@0: void nsWebBrowserPersist::SetApplyConversionIfNeeded(nsIChannel *aChannel) michael@0: { michael@0: nsresult rv = NS_OK; michael@0: nsCOMPtr encChannel = do_QueryInterface(aChannel, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: // Set the default conversion preference: michael@0: encChannel->SetApplyConversion(false); michael@0: michael@0: nsCOMPtr thisURI; michael@0: aChannel->GetURI(getter_AddRefs(thisURI)); michael@0: nsCOMPtr sourceURL(do_QueryInterface(thisURI)); michael@0: if (!sourceURL) michael@0: return; michael@0: nsAutoCString extension; michael@0: sourceURL->GetFileExtension(extension); michael@0: michael@0: nsCOMPtr encEnum; michael@0: encChannel->GetContentEncodings(getter_AddRefs(encEnum)); michael@0: if (!encEnum) michael@0: return; michael@0: nsCOMPtr helperAppService = michael@0: do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: bool hasMore; michael@0: rv = encEnum->HasMore(&hasMore); michael@0: if (NS_SUCCEEDED(rv) && hasMore) michael@0: { michael@0: nsAutoCString encType; michael@0: rv = encEnum->GetNext(encType); michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: bool applyConversion = false; michael@0: rv = helperAppService->ApplyDecodingForExtension(extension, encType, michael@0: &applyConversion); michael@0: if (NS_SUCCEEDED(rv)) michael@0: encChannel->SetApplyConversion(applyConversion); michael@0: } michael@0: } michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: michael@0: nsEncoderNodeFixup::nsEncoderNodeFixup() : mWebBrowserPersist(nullptr) michael@0: { michael@0: } michael@0: michael@0: michael@0: nsEncoderNodeFixup::~nsEncoderNodeFixup() michael@0: { michael@0: } michael@0: michael@0: michael@0: NS_IMPL_ADDREF(nsEncoderNodeFixup) michael@0: NS_IMPL_RELEASE(nsEncoderNodeFixup) michael@0: michael@0: michael@0: NS_INTERFACE_MAP_BEGIN(nsEncoderNodeFixup) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDocumentEncoderNodeFixup) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoderNodeFixup) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: michael@0: NS_IMETHODIMP nsEncoderNodeFixup::FixupNode( michael@0: nsIDOMNode *aNode, bool *aSerializeCloneKids, nsIDOMNode **aOutNode) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aNode); michael@0: NS_ENSURE_ARG_POINTER(aOutNode); michael@0: NS_ENSURE_TRUE(mWebBrowserPersist, NS_ERROR_FAILURE); michael@0: michael@0: *aOutNode = nullptr; michael@0: michael@0: // Test whether we need to fixup the node michael@0: uint16_t type = 0; michael@0: aNode->GetNodeType(&type); michael@0: if (type == nsIDOMNode::ELEMENT_NODE || michael@0: type == nsIDOMNode::PROCESSING_INSTRUCTION_NODE) michael@0: { michael@0: return mWebBrowserPersist->CloneNodeWithFixedUpAttributes(aNode, aSerializeCloneKids, aOutNode); michael@0: } michael@0: michael@0: return NS_OK; michael@0: }