diff options
Diffstat (limited to 'mailnews/compose/src/nsURLFetcher.cpp')
-rw-r--r-- | mailnews/compose/src/nsURLFetcher.cpp | 526 |
1 files changed, 526 insertions, 0 deletions
diff --git a/mailnews/compose/src/nsURLFetcher.cpp b/mailnews/compose/src/nsURLFetcher.cpp new file mode 100644 index 0000000000..b564ab9a4f --- /dev/null +++ b/mailnews/compose/src/nsURLFetcher.cpp @@ -0,0 +1,526 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsURLFetcher.h" + +#include "msgCore.h" // for pre-compiled headers +#include "nsCOMPtr.h" +#include "nsNullPrincipal.h" +#include <stdio.h> +#include "nscore.h" +#include "nsIFactory.h" +#include "nsISupports.h" +#include "prmem.h" +#include "plstr.h" +#include "nsIComponentManager.h" +#include "nsStringGlue.h" +#include "nsIIOService.h" +#include "nsIChannel.h" +#include "nsNetUtil.h" +#include "nsMimeTypes.h" +#include "nsIHttpChannel.h" +#include "nsIWebProgress.h" +#include "nsMsgAttachmentHandler.h" +#include "nsMsgSend.h" +#include "nsISeekableStream.h" +#include "nsIStreamConverterService.h" +#include "nsIMsgProgress.h" +#include "nsMsgUtils.h" + +NS_IMPL_ISUPPORTS(nsURLFetcher, + nsIURLFetcher, + nsIStreamListener, + nsIRequestObserver, + nsIURIContentListener, + nsIInterfaceRequestor, + nsIWebProgressListener, + nsISupportsWeakReference) + + +/* + * Inherited methods for nsMimeConverter + */ +nsURLFetcher::nsURLFetcher() +{ + // Init member variables... + mTotalWritten = 0; + mBuffer = nullptr; + mBufferSize = 0; + mStillRunning = true; + mCallback = nullptr; + mOnStopRequestProcessed = false; + mIsFile=false; + nsURLFetcherStreamConsumer *consumer = new nsURLFetcherStreamConsumer(this); + mConverter = do_QueryInterface(consumer); +} + +nsURLFetcher::~nsURLFetcher() +{ + mStillRunning = false; + + PR_FREEIF(mBuffer); + // Remove the DocShell as a listener of the old WebProgress... + if (mLoadCookie) + { + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie)); + + if (webProgress) + webProgress->RemoveProgressListener(this); + } +} + +NS_IMETHODIMP nsURLFetcher::GetInterface(const nsIID & aIID, void * *aInstancePtr) +{ + NS_ENSURE_ARG_POINTER(aInstancePtr); + return QueryInterface(aIID, aInstancePtr); +} + +// nsIURIContentListener support +NS_IMETHODIMP +nsURLFetcher::OnStartURIOpen(nsIURI* aURI, bool* aAbortOpen) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::IsPreferred(const char * aContentType, + char ** aDesiredContentType, + bool * aCanHandleContent) + +{ + return CanHandleContent(aContentType, true, aDesiredContentType, + aCanHandleContent); +} + +NS_IMETHODIMP +nsURLFetcher::CanHandleContent(const char * aContentType, + bool aIsContentPreferred, + char ** aDesiredContentType, + bool * aCanHandleContent) + +{ + if (!mIsFile && PL_strcasecmp(aContentType, MESSAGE_RFC822) == 0) + *aDesiredContentType = strdup("text/html"); + + // since we explicilty loaded the url, we always want to handle it! + *aCanHandleContent = true; + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::DoContent(const nsACString& aContentType, + bool aIsContentPreferred, + nsIRequest *request, + nsIStreamListener ** aContentHandler, + bool * aAbortProcess) +{ + nsresult rv = NS_OK; + + if (aAbortProcess) + *aAbortProcess = false; + QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aContentHandler); + + /* + Check the content-type to see if we need to insert a converter + */ + if (PL_strcasecmp(PromiseFlatCString(aContentType).get(), UNKNOWN_CONTENT_TYPE) == 0 || + PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_MIXED_REPLACE) == 0 || + PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_MIXED) == 0 || + PL_strcasecmp(PromiseFlatCString(aContentType).get(), MULTIPART_BYTERANGES) == 0) + { + rv = InsertConverter(PromiseFlatCString(aContentType).get()); + if (NS_SUCCEEDED(rv)) + mConverterContentType = PromiseFlatCString(aContentType).get(); + } + + return rv; +} + +NS_IMETHODIMP +nsURLFetcher::GetParentContentListener(nsIURIContentListener** aParent) +{ + *aParent = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::SetParentContentListener(nsIURIContentListener* aParent) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::GetLoadCookie(nsISupports ** aLoadCookie) +{ + *aLoadCookie = mLoadCookie; + NS_IF_ADDREF(*aLoadCookie); + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::SetLoadCookie(nsISupports * aLoadCookie) +{ + // Remove the DocShell as a listener of the old WebProgress... + if (mLoadCookie) + { + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie)); + + if (webProgress) + webProgress->RemoveProgressListener(this); + } + + mLoadCookie = aLoadCookie; + + // Add the DocShell as a listener to the new WebProgress... + if (mLoadCookie) + { + nsCOMPtr<nsIWebProgress> webProgress(do_QueryInterface(mLoadCookie)); + + if (webProgress) + webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL); + } + return NS_OK; + +} + +nsresult +nsURLFetcher::StillRunning(bool *running) +{ + *running = mStillRunning; + return NS_OK; +} + + +// Methods for nsIStreamListener... +nsresult +nsURLFetcher::OnDataAvailable(nsIRequest *request, nsISupports * ctxt, nsIInputStream *aIStream, + uint64_t sourceOffset, uint32_t aLength) +{ + /* let our converter or consumer process the data */ + if (!mConverter) + return NS_ERROR_FAILURE; + + return mConverter->OnDataAvailable(request, ctxt, aIStream, sourceOffset, aLength); +} + + +// Methods for nsIStreamObserver +nsresult +nsURLFetcher::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + /* check if the user has canceld the operation */ + if (mTagData) + { + nsCOMPtr<nsIMsgSend> sendPtr; + mTagData->GetMimeDeliveryState(getter_AddRefs(sendPtr)); + if (sendPtr) + { + nsCOMPtr<nsIMsgProgress> progress; + sendPtr->GetProgress(getter_AddRefs(progress)); + if (progress) + { + bool cancel = false; + progress->GetProcessCanceledByUser(&cancel); + if (cancel) + return request->Cancel(NS_ERROR_ABORT); + } + } + mTagData->mRequest = request; + } + + /* call our converter or consumer */ + if (mConverter) + return mConverter->OnStartRequest(request, ctxt); + + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnStopRequest(nsIRequest *request, nsISupports * ctxt, nsresult aStatus) +{ + // it's possible we could get in here from the channel calling us with an OnStopRequest and from our + // onStatusChange method (in the case of an error). So we should protect against this to make sure we + // don't process the on stop request twice... + + if (mOnStopRequestProcessed) + return NS_OK; + + mOnStopRequestProcessed = true; + + /* first, call our converter or consumer */ + if (mConverter) + (void) mConverter->OnStopRequest(request, ctxt, aStatus); + + if (mTagData) + mTagData->mRequest = nullptr; + + // + // Now complete the stream! + // + mStillRunning = false; + + // time to close the output stream... + if (mOutStream) + { + mOutStream->Close(); + mOutStream = nullptr; + + /* In case of multipart/x-mixed-replace, we need to truncate the file to the current part size */ + if (MsgLowerCaseEqualsLiteral(mConverterContentType, MULTIPART_MIXED_REPLACE)) + { + mLocalFile->SetFileSize(mTotalWritten); + } + } + + // Now if there is a callback, we need to call it... + if (mCallback) + mCallback (aStatus, mContentType, mCharset, mTotalWritten, nullptr, mTagData); + + // Time to return... + return NS_OK; +} + +nsresult +nsURLFetcher::Initialize(nsIFile *localFile, + nsIOutputStream *outputStream, + nsAttachSaveCompletionCallback cb, + nsMsgAttachmentHandler *tagData) +{ + if (!outputStream || !localFile) + return NS_ERROR_INVALID_ARG; + + mOutStream = outputStream; + mLocalFile = localFile; + mCallback = cb; //JFD: Please, no more callback, use a listener... + mTagData = tagData; + return NS_OK; +} + +nsresult +nsURLFetcher::FireURLRequest(nsIURI *aURL, nsIFile *localFile, nsIOutputStream *outputStream, + nsAttachSaveCompletionCallback cb, nsMsgAttachmentHandler *tagData) +{ + nsresult rv; + + rv = Initialize(localFile, outputStream, cb, tagData); + NS_ENSURE_SUCCESS(rv, rv); + + //check to see if aURL is a local file or not + aURL->SchemeIs("file", &mIsFile); + + // we're about to fire a new url request so make sure the on stop request flag is cleared... + mOnStopRequestProcessed = false; + + // let's try uri dispatching... + nsCOMPtr<nsIURILoader> pURILoader (do_GetService(NS_URI_LOADER_CONTRACTID)); + NS_ENSURE_TRUE(pURILoader, NS_ERROR_FAILURE); + + nsCOMPtr<nsIPrincipal> nullPrincipal = + do_CreateInstance("@mozilla.org/nullprincipal;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIChannel> channel; + rv = NS_NewChannel(getter_AddRefs(channel), + aURL, + nullPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + this); // aCallbacks + NS_ENSURE_SUCCESS(rv, rv); + + return pURILoader->OpenURI(channel, false, this); +} + +nsresult +nsURLFetcher::InsertConverter(const char * aContentType) +{ + nsresult rv; + + nsCOMPtr<nsIStreamConverterService> convServ(do_GetService("@mozilla.org/streamConverters;1", &rv)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr<nsIStreamListener> toListener(mConverter); + nsCOMPtr<nsIStreamListener> fromListener; + + rv = convServ->AsyncConvertData(aContentType, + "*/*", + toListener, + nullptr, + getter_AddRefs(fromListener)); + if (NS_SUCCEEDED(rv)) + mConverter = fromListener; + } + + return rv; +} + +// web progress listener implementation + +NS_IMETHODIMP +nsURLFetcher::OnProgressChange(nsIWebProgress *aProgress, nsIRequest *aRequest, + int32_t aCurSelfProgress, int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, int32_t aMaxTotalProgress) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnStateChange(nsIWebProgress *aProgress, nsIRequest *aRequest, + uint32_t aStateFlags, nsresult aStatus) +{ + // all we care about is the case where an error occurred (as in we were unable to locate the + // the url.... + + if (NS_FAILED(aStatus)) + OnStopRequest(aRequest, nullptr, aStatus); + + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI *aURI, + uint32_t aFlags) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsURLFetcher::OnSecurityChange(nsIWebProgress *aWebProgress, + nsIRequest *aRequest, + uint32_t state) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + + +/** + * Stream consumer used for handling special content type like multipart/x-mixed-replace + */ + +NS_IMPL_ISUPPORTS(nsURLFetcherStreamConsumer, nsIStreamListener, nsIRequestObserver) + +nsURLFetcherStreamConsumer::nsURLFetcherStreamConsumer(nsURLFetcher* urlFetcher) : + mURLFetcher(urlFetcher) +{ +} + +nsURLFetcherStreamConsumer::~nsURLFetcherStreamConsumer() +{ +} + +/** nsIRequestObserver methods **/ + +/* void onStartRequest (in nsIRequest request, in nsISupports ctxt); */ +NS_IMETHODIMP nsURLFetcherStreamConsumer::OnStartRequest(nsIRequest *aRequest, nsISupports *ctxt) +{ + if (!mURLFetcher || !mURLFetcher->mOutStream) + return NS_ERROR_FAILURE; + + /* In case of multipart/x-mixed-replace, we need to erase the output file content */ + if (MsgLowerCaseEqualsLiteral(mURLFetcher->mConverterContentType, MULTIPART_MIXED_REPLACE)) + { + nsCOMPtr<nsISeekableStream> seekStream = do_QueryInterface(mURLFetcher->mOutStream); + if (seekStream) + seekStream->Seek(nsISeekableStream::NS_SEEK_SET, 0); + mURLFetcher->mTotalWritten = 0; + } + + return NS_OK; +} + +/* void onStopRequest (in nsIRequest request, in nsISupports ctxt, in nsresult status); */ +NS_IMETHODIMP nsURLFetcherStreamConsumer::OnStopRequest(nsIRequest *aRequest, nsISupports *ctxt, nsresult status) +{ + if (!mURLFetcher) + return NS_ERROR_FAILURE; + + // Check the content type! + nsAutoCString contentType; + nsAutoCString charset; + + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if(!channel) return NS_ERROR_FAILURE; + + if (NS_SUCCEEDED(channel->GetContentType(contentType)) && + !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) + { + nsAutoCString uriSpec; + nsCOMPtr <nsIURI> channelURI; + channel->GetURI(getter_AddRefs(channelURI)); + nsresult rv = channelURI->GetSpec(uriSpec); + NS_ENSURE_SUCCESS(rv, rv); + if (uriSpec.Find("&realtype=message/rfc822") >= 0) + mURLFetcher->mContentType = MESSAGE_RFC822; + else + mURLFetcher->mContentType = contentType; + } + + if (NS_SUCCEEDED(channel->GetContentCharset(charset)) && !charset.IsEmpty()) + { + mURLFetcher->mCharset = charset; + } + + return NS_OK; +} + +/** nsIStreamListener methods **/ + +/* void onDataAvailable (in nsIRequest request, in nsISupports ctxt, in nsIInputStream inStr, in unsigned long long sourceOffset, in unsigned long count); */ +NS_IMETHODIMP nsURLFetcherStreamConsumer::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, uint64_t sourceOffset, uint32_t count) +{ + uint32_t readLen = count; + uint32_t wroteIt; + + if (!mURLFetcher) + return NS_ERROR_FAILURE; + + if (!mURLFetcher->mOutStream) + return NS_ERROR_INVALID_ARG; + + if (mURLFetcher->mBufferSize < count) + { + PR_FREEIF(mURLFetcher->mBuffer); + + if (count > 0x1000) + mURLFetcher->mBufferSize = count; + else + mURLFetcher->mBufferSize = 0x1000; + + mURLFetcher->mBuffer = (char *)PR_Malloc(mURLFetcher->mBufferSize); + if (!mURLFetcher->mBuffer) + return NS_ERROR_OUT_OF_MEMORY; /* we couldn't allocate the object */ + } + + // read the data from the input stram... + nsresult rv = inStr->Read(mURLFetcher->mBuffer, count, &readLen); + if (NS_FAILED(rv)) + return rv; + + // write to the output file... + mURLFetcher->mOutStream->Write(mURLFetcher->mBuffer, readLen, &wroteIt); + + if (wroteIt != readLen) + return NS_ERROR_FAILURE; + else + { + mURLFetcher->mTotalWritten += wroteIt; + return NS_OK; + } +} |