/* -*- 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 <pp@ludusdesign.com>
*
* 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 <stdio.h>
#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<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, 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<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(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<nsIMsgSend> sendPtr;
attachmentHdl->GetMimeDeliveryState(getter_AddRefs(sendPtr));
if (sendPtr)
{
nsCOMPtr<nsIMsgProgress> 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<nsIURILoader> pURILoader (do_GetService(NS_URI_LOADER_CONTRACTID));
NS_ENSURE_TRUE(pURILoader, NS_ERROR_FAILURE);
nsCOMPtr<nsIChannel> 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<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,
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<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!
nsCAutoString contentType;
nsCAutoString charset;
nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest);
if(!channel) return NS_ERROR_FAILURE;
if (NS_SUCCEEDED(channel->GetContentType(contentType)) &&
!contentType.EqualsLiteral(UNKNOWN_CONTENT_TYPE))
{
nsCAutoString uriSpec;
nsCOMPtr <nsIURI> 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;
}
}