/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is mozilla.org code. * * The Initial Developer of the Original Code is * Netscape Communications Corporation. * Portions created by the Initial Developer are Copyright (C) 1998 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Pierre Phaneuf * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsURLFetcher.h" #include "msgCore.h" // for pre-compiled headers #include "nsCOMPtr.h" #include #include "nscore.h" #include "nsIFactory.h" #include "nsISupports.h" #include "comi18n.h" #include "prmem.h" #include "plstr.h" #include "nsIComponentManager.h" #include "nsString.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" NS_IMPL_ISUPPORTS7(nsURLFetcher, nsIURLFetcher, nsIStreamListener, nsIRequestObserver, nsIURIContentListener, nsIInterfaceRequestor, nsIWebProgressListener, nsISupportsWeakReference) /* * Inherited methods for nsMimeConverter */ nsURLFetcher::nsURLFetcher() { #if defined(DEBUG_ducarroz) printf("CREATE nsURLFetcher: %x\n", this); #endif // Init member variables... mTotalWritten = 0; mBuffer = nsnull; mBufferSize = 0; mStillRunning = PR_TRUE; mCallback = nsnull; mOnStopRequestProcessed = PR_FALSE; mIsFile=PR_FALSE; nsURLFetcherStreamConsumer *consumer = new nsURLFetcherStreamConsumer(this); mConverter = do_QueryInterface(consumer); } nsURLFetcher::~nsURLFetcher() { #if defined(DEBUG_ducarroz) printf("DISPOSE nsURLFetcher: %x\n", this); #endif mStillRunning = PR_FALSE; PR_FREEIF(mBuffer); // Remove the DocShell as a listener of the old WebProgress... if (mLoadCookie) { nsCOMPtr 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, PRBool* aAbortOpen) { return NS_OK; } NS_IMETHODIMP nsURLFetcher::IsPreferred(const char * aContentType, char ** aDesiredContentType, PRBool * aCanHandleContent) { return CanHandleContent(aContentType, PR_TRUE, aDesiredContentType, aCanHandleContent); } NS_IMETHODIMP nsURLFetcher::CanHandleContent(const char * aContentType, PRBool aIsContentPreferred, char ** aDesiredContentType, PRBool * 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 = PR_TRUE; return NS_OK; } NS_IMETHODIMP nsURLFetcher::DoContent(const char * aContentType, PRBool aIsContentPreferred, nsIRequest *request, nsIStreamListener ** aContentHandler, PRBool * aAbortProcess) { nsresult rv = NS_OK; if (aAbortProcess) *aAbortProcess = PR_FALSE; QueryInterface(NS_GET_IID(nsIStreamListener), (void **) aContentHandler); /* Check the content-type to see if we need to insert a converter */ if (PL_strcasecmp(aContentType, UNKNOWN_CONTENT_TYPE) == 0 || PL_strcasecmp(aContentType, MULTIPART_MIXED_REPLACE) == 0 || PL_strcasecmp(aContentType, MULTIPART_MIXED) == 0 || PL_strcasecmp(aContentType, MULTIPART_BYTERANGES) == 0) { rv = InsertConverter(aContentType); if (NS_SUCCEEDED(rv)) mConverterContentType = aContentType; } return rv; } NS_IMETHODIMP nsURLFetcher::GetParentContentListener(nsIURIContentListener** aParent) { *aParent = nsnull; 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 webProgress(do_QueryInterface(mLoadCookie)); if (webProgress) webProgress->RemoveProgressListener(this); } mLoadCookie = aLoadCookie; // Add the DocShell as a listener to the new WebProgress... if (mLoadCookie) { nsCOMPtr webProgress(do_QueryInterface(mLoadCookie)); if (webProgress) webProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_ALL); } return NS_OK; } nsresult nsURLFetcher::StillRunning(PRBool *running) { *running = mStillRunning; return NS_OK; } // Methods for nsIStreamListener... nsresult nsURLFetcher::OnDataAvailable(nsIRequest *request, nsISupports * ctxt, nsIInputStream *aIStream, PRUint32 sourceOffset, PRUint32 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 */ nsMsgAttachmentHandler *attachmentHdl = (nsMsgAttachmentHandler *)mTagData; if (attachmentHdl) { nsCOMPtr sendPtr; attachmentHdl->GetMimeDeliveryState(getter_AddRefs(sendPtr)); if (sendPtr) { nsCOMPtr progress; sendPtr->GetProgress(getter_AddRefs(progress)); if (progress) { PRBool cancel = PR_FALSE; progress->GetProcessCanceledByUser(&cancel); if (cancel) return request->Cancel(NS_ERROR_ABORT); } } attachmentHdl->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) { #if defined(DEBUG_ducarroz) printf("nsURLFetcher::OnStopRequest()\n"); #endif nsresult rv = NS_OK; // 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 = PR_TRUE; /* first, call our converter or consumer */ if (mConverter) rv = mConverter->OnStopRequest(request, ctxt, aStatus); nsMsgAttachmentHandler *attachmentHdl = (nsMsgAttachmentHandler *)mTagData; if (attachmentHdl) attachmentHdl->mRequest = nsnull; // // Now complete the stream! // mStillRunning = PR_FALSE; // time to close the output stream... if (mOutStream) { mOutStream->Close(); mOutStream = nsnull; /* In case of multipart/x-mixed-replace, we need to truncate the file to the current part size */ if (mConverterContentType.LowerCaseEqualsLiteral(MULTIPART_MIXED_REPLACE)) { PRInt64 fileSize; LL_I2L(fileSize, mTotalWritten); mLocalFile->SetFileSize(fileSize); } } // Now if there is a callback, we need to call it... if (mCallback) mCallback (aStatus, mContentType, mCharset, mTotalWritten, nsnull, mTagData); // Time to return... return NS_OK; } nsresult nsURLFetcher::Initialize(nsILocalFile *localFile, nsIFileOutputStream *outputStream, nsAttachSaveCompletionCallback cb, void *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; //JFD: TODO, WE SHOULD USE A NSCOMPTR to hold this stuff!!! return NS_OK; } nsresult nsURLFetcher::FireURLRequest(nsIURI *aURL, nsILocalFile *localFile, nsIFileOutputStream *outputStream, nsAttachSaveCompletionCallback cb, void *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 = PR_FALSE; // let's try uri dispatching... nsCOMPtr pURILoader (do_GetService(NS_URI_LOADER_CONTRACTID)); NS_ENSURE_TRUE(pURILoader, NS_ERROR_FAILURE); nsCOMPtr channel; NS_ENSURE_SUCCESS(NS_NewChannel(getter_AddRefs(channel), aURL, nsnull, nsnull, this), NS_ERROR_FAILURE); return pURILoader->OpenURI(channel, PR_FALSE, this); } nsresult nsURLFetcher::InsertConverter(const char * aContentType) { nsresult rv; nsCOMPtr convServ(do_GetService("@mozilla.org/streamConverters;1", &rv)); if (NS_SUCCEEDED(rv)) { nsCOMPtr toListener(mConverter); nsCOMPtr fromListener; rv = convServ->AsyncConvertData(aContentType, "*/*", toListener, nsnull, getter_AddRefs(fromListener)); if (NS_SUCCEEDED(rv)) mConverter = fromListener; } return rv; } // web progress listener implementation NS_IMETHODIMP nsURLFetcher::OnProgressChange(nsIWebProgress *aProgress, nsIRequest *aRequest, PRInt32 aCurSelfProgress, PRInt32 aMaxSelfProgress, PRInt32 aCurTotalProgress, PRInt32 aMaxTotalProgress) { return NS_OK; } NS_IMETHODIMP nsURLFetcher::OnStateChange(nsIWebProgress *aProgress, nsIRequest *aRequest, PRUint32 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, nsnull, aStatus); return NS_OK; } NS_IMETHODIMP nsURLFetcher::OnLocationChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsIURI *aURI) { NS_NOTREACHED("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsURLFetcher::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const PRUnichar* aMessage) { NS_NOTREACHED("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsURLFetcher::OnSecurityChange(nsIWebProgress *aWebProgress, nsIRequest *aRequest, PRUint32 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_ISUPPORTS2(nsURLFetcherStreamConsumer, nsIStreamListener, nsIRequestObserver) nsURLFetcherStreamConsumer::nsURLFetcherStreamConsumer(nsURLFetcher* urlFetcher) : mURLFetcher(urlFetcher) { #if defined(DEBUG_ducarroz) printf("CREATE nsURLFetcherStreamConsumer: %x\n", this); #endif } nsURLFetcherStreamConsumer::~nsURLFetcherStreamConsumer() { #if defined(DEBUG_ducarroz) printf("DISPOSE nsURLFetcherStreamConsumer: %x\n", this); #endif } /** 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 (mURLFetcher->mConverterContentType.LowerCaseEqualsLiteral(MULTIPART_MIXED_REPLACE)) { nsCOMPtr 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! nsCAutoString contentType; nsCAutoString charset; nsCOMPtr channel = do_QueryInterface(aRequest); if(!channel) return NS_ERROR_FAILURE; if (NS_SUCCEEDED(channel->GetContentType(contentType)) && !contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE)) { nsCAutoString uriSpec; nsCOMPtr channelURI; channel->GetURI(getter_AddRefs(channelURI)); channelURI->GetSpec(uriSpec); 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 sourceOffset, in unsigned long count); */ NS_IMETHODIMP nsURLFetcherStreamConsumer::OnDataAvailable(nsIRequest *aRequest, nsISupports *ctxt, nsIInputStream *inStr, PRUint32 sourceOffset, PRUint32 count) { PRUint32 readLen = count; PRUint32 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; } }